openapi: 3.1.0
info:
  title: AccessPreflight Integration API
  version: 1.0.0-beta.1
  description: Project API-key endpoints for accessibility preflight scans, results, reports, policy metadata, webhooks, and usage.
  license:
    name: Proprietary
    identifier: LicenseRef-Proprietary
jsonSchemaDialect: https://json-schema.org/draft/2020-12/schema
servers:
  - url: https://api.accesspreflight.com
    description: EU production API
security:
  - bearerAuth: []
tags:
  - name: Uploads
    description: Server-mediated staging, integrity validation, sealing, status, and deletion for supplied content.
  - name: Scans
    description: Asynchronous accessibility preflight, findings, coverage, comparisons, cancellation, and reports.
  - name: Profiles
    description: Immutable technical and legal-context profiles, mappings, and coverage denominators.
  - name: Rule packs
    description: Customer-owned immutable policy versions that select and customize preflight rules.
  - name: Suppressions
    description: Auditable, optionally expiring exceptions that retain the underlying finding.
  - name: Webhooks
    description: Signed endpoint lifecycle, delivery history, tests, rotation, and replay.
  - name: Usage
    description: Weighted-credit usage, allowance, ledger summaries, and threshold state.
  - name: Evidence
    description: Public verification keys for signed immutable evidence manifests.
paths:
  /v1/uploads:
    post:
      operationId: createUpload
      tags:
        - Uploads
      summary: Create a single-purpose server-mediated upload intent
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateUploadRequest'
            examples:
              pdf:
                summary: Create a retained PDF upload slot
                value:
                  project_id: prj_018f7e8a7d7b7c008000000000000005
                  environment: production
                  file_name: accessibility-statement.pdf
                  declared_media_type: application/pdf
                  size_bytes: 482103
                  sha256: 0f0e0d0c0b0a090807060504030201000f0e0d0c0b0a09080706050403020100
                  retention: 7d
      responses:
        '201':
          description: Upload slot created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Upload'
        '403':
          $ref: '#/components/responses/Forbidden'
        '422':
          $ref: '#/components/responses/ValidationError'
        '429':
          $ref: '#/components/responses/TooManyRequests'
  /v1/uploads/{upload_id}:
    parameters:
      - $ref: '#/components/parameters/UploadID'
    get:
      operationId: getUpload
      tags:
        - Uploads
      summary: Read upload validation status
      responses:
        '200':
          description: Tenant-scoped upload state.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Upload'
        '404':
          $ref: '#/components/responses/NotFound'
    delete:
      operationId: deleteUpload
      tags:
        - Uploads
      summary: Delete source content
      responses:
        '202':
          description: Deletion accepted.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeletionRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
  /v1/uploads/{upload_id}/content:
    parameters:
      - $ref: '#/components/parameters/UploadID'
    put:
      operationId: putUploadContent
      tags:
        - Uploads
      summary: Stream content into private staging storage through the API
      description: |
        The request body must be the raw file bytes, not multipart form data.
        Content-Type and Content-Length must exactly match the upload intent.
        Content-Digest must use the RFC form `sha-256=:base64:` and must match
        both the intent and the bytes observed while the API streams the body
        to private object storage. Object-store credentials and metadata are
        never exposed. Exact Idempotency-Key replays return the original 204;
        concurrent requests and key/body conflicts return 409.
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
        - name: Content-Length
          in: header
          required: true
          schema:
            type: integer
            minimum: 1
            maximum: 52428800
        - name: Content-Digest
          in: header
          required: true
          description: RFC Content-Digest containing exactly one SHA-256 digest.
          schema:
            type: string
            pattern: ^sha-256=:[A-Za-z0-9+/]{43}=:[ ]*$
            maxLength: 64
      requestBody:
        required: true
        content:
          application/pdf:
            schema:
              type: string
              format: binary
          application/vnd.openxmlformats-officedocument.wordprocessingml.document:
            schema:
              type: string
              format: binary
          text/html:
            schema:
              type: string
              format: binary
          application/zip:
            schema:
              type: string
              format: binary
      responses:
        '204':
          description: Content stored and integrity checked; call the completion endpoint to finalize it.
          headers:
            Idempotent-Replayed:
              description: Present and true when this response replays previously accepted content.
              schema:
                type: string
                const: 'true'
        '400':
          description: Idempotency-Key or Content-Digest is missing or malformed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '411':
          description: Content-Length is required.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '413':
          description: Content exceeds the upload intent or platform limit.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '415':
          description: Content-Type does not match the upload intent.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '422':
          $ref: '#/components/responses/ValidationError'
  /v1/uploads/{upload_id}/complete:
    parameters:
      - $ref: '#/components/parameters/UploadID'
    post:
      operationId: completeUpload
      tags:
        - Uploads
      summary: Seal and validate an uploaded object
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '202':
          description: Intake validation queued.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Upload'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          description: |
            The upload is not ready to complete, or malware scanning is still
            pending. A malware_scan_pending response includes Retry-After.
          headers:
            X-Request-ID:
              $ref: '#/components/headers/RequestID'
            Retry-After:
              description: Seconds before retrying while malware scanning remains pending.
              schema:
                type: string
                pattern: ^[1-9][0-9]*$
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '422':
          $ref: '#/components/responses/ValidationError'
  /v1/scans:
    get:
      operationId: listScans
      tags:
        - Scans
      summary: List project scan history
      parameters:
        - $ref: '#/components/parameters/ProjectIDQuery'
        - $ref: '#/components/parameters/Cursor'
        - $ref: '#/components/parameters/Limit'
        - name: environment
          in: query
          schema:
            $ref: '#/components/schemas/ProjectEnvironment'
        - name: status
          in: query
          schema:
            $ref: '#/components/schemas/ScanStatus'
      responses:
        '200':
          description: Reverse-chronological scan page.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ScanPage'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    post:
      operationId: createScan
      tags:
        - Scans
      summary: Create an asynchronous accessibility preflight
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateScanRequest'
            examples:
              pdfQualityGate:
                summary: Scan an uploaded PDF with a blocking quality gate
                value:
                  project_id: prj_018f7e8a7d7b7c008000000000000005
                  environment: production
                  asset:
                    type: upload
                    upload_id: upl_018f7e8a7d7b7c008000000000000021
                  profiles:
                    - pdfua-1-machine@1.0.0
                    - en-301-549-v3.2.1-nonweb-document@1.0.0
                  options:
                    include_repair_plan: true
                    include_screenshots: false
                  quality_gate:
                    block_on:
                      - blocker
                      - critical
                    block_on_new_only: false
                    require_manual_checklist_acknowledgement: true
                    fail_on_incomplete: true
      responses:
        '202':
          description: Scan accepted.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Scan'
        '403':
          $ref: '#/components/responses/Forbidden'
        '409':
          $ref: '#/components/responses/Conflict'
        '422':
          $ref: '#/components/responses/ValidationError'
        '429':
          $ref: '#/components/responses/TooManyRequests'
  /v1/scans/{scan_id}:
    parameters:
      - $ref: '#/components/parameters/ScanID'
    get:
      operationId: getScan
      tags:
        - Scans
      summary: Read scan status and summary
      responses:
        '200':
          description: Tenant-scoped scan.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Scan'
        '404':
          $ref: '#/components/responses/NotFound'
  /v1/scans/{scan_id}/cancel:
    parameters:
      - $ref: '#/components/parameters/ScanID'
    post:
      operationId: cancelScan
      tags:
        - Scans
      summary: Request cancellation
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                reason:
                  type: string
                  maxLength: 500
                  description: |
                    Optional customer context. The server trims and collapses
                    whitespace and removes control characters before storing
                    it with the cancellation state event and audit record.
      responses:
        '202':
          description: Cancellation requested or scan already terminal.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Scan'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '422':
          $ref: '#/components/responses/ValidationError'
        '429':
          $ref: '#/components/responses/TooManyRequests'
  /v1/scans/{scan_id}/findings:
    parameters:
      - $ref: '#/components/parameters/ScanID'
    get:
      operationId: listScanFindings
      tags:
        - Scans
      summary: List normalized findings
      parameters:
        - $ref: '#/components/parameters/Cursor'
        - $ref: '#/components/parameters/Limit'
        - name: severity
          in: query
          schema:
            $ref: '#/components/schemas/Severity'
        - name: automation
          in: query
          schema:
            $ref: '#/components/schemas/Automation'
        - name: baseline_state
          in: query
          schema:
            type: string
            enum:
              - new
              - recurring
              - not_compared
      responses:
        '200':
          description: Findings page.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FindingPage'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
  /v1/scans/{scan_id}/coverage:
    parameters:
      - $ref: '#/components/parameters/ScanID'
    get:
      operationId: getScanCoverage
      tags:
        - Scans
      summary: Read detailed automated and manual coverage
      responses:
        '200':
          description: Coverage summary and items.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CoverageResponse'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
  /v1/scans/{scan_id}/reports/{format}:
    parameters:
      - $ref: '#/components/parameters/ScanID'
      - name: format
        in: path
        required: true
        schema:
          type: string
          enum:
            - json
            - html
            - xlsx
            - acr
            - evidence
    get:
      operationId: getScanReport
      tags:
        - Scans
      summary: Download an immutable report, repair workbook, Section 508 ACR evidence workbook, or evidence manifest
      responses:
        '200':
          description: Requested immutable artifact.
          headers:
            ETag:
              description: SHA-256-backed immutable artifact validator.
              schema:
                type: string
          content:
            application/vnd.accesspreflight.report+json:
              schema:
                $ref: '#/components/schemas/canonical-report-v1.schema'
            text/html:
              schema:
                type: string
            application/vnd.openxmlformats-officedocument.spreadsheetml.sheet:
              schema:
                type: string
                format: binary
            application/vnd.accesspreflight.evidence+json:
              schema:
                $ref: '#/components/schemas/evidence-manifest-v1.schema'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
  /v1/scans/{scan_id}/diff:
    parameters:
      - $ref: '#/components/parameters/ScanID'
      - name: baseline
        in: query
        required: true
        schema:
          $ref: '#/components/schemas/ScanIDValue'
    get:
      operationId: diffScan
      tags:
        - Scans
      summary: Compare fingerprints to a compatible baseline
      responses:
        '200':
          description: New, recurring, and resolved fingerprint sets.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ScanDiff'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
  /v1/profiles:
    get:
      operationId: listProfiles
      tags:
        - Profiles
      summary: List immutable profile versions
      description: |
        Returns only versions selectable by this deployment. The private-beta
        gate may expose draft mappings pending review; preview and retired
        mappings remain unavailable.
      parameters:
        - name: asset_type
          in: query
          schema:
            type: string
            enum:
              - pdf
              - docx
              - url
              - html
      responses:
        '200':
          description: Available built-in profiles.
          content:
            application/json:
              schema:
                type: object
                additionalProperties: false
                required:
                  - data
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Profile'
  /v1/profiles/{profile_id}:
    parameters:
      - name: profile_id
        in: path
        required: true
        schema:
          type: string
          pattern: ^[a-z0-9]+(?:[._-][a-z0-9]+)*@[0-9]+(?:\.[0-9]+){1,2}(?:-[0-9A-Za-z.-]+)?$
          maxLength: 200
    get:
      operationId: getProfile
      tags:
        - Profiles
      summary: Read profile source metadata and coverage
      description: |
        Returns a concrete version only when it is selectable by this
        deployment's profile-release gate.
      responses:
        '200':
          description: Concrete immutable profile.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Profile'
        '404':
          $ref: '#/components/responses/NotFound'
  /v1/profiles/{profile_id}/mapping:
    parameters:
      - name: profile_id
        in: path
        required: true
        description: Concrete immutable profile identifier and version. `latest` is not accepted for artifact retrieval.
        schema:
          type: string
          pattern: ^[a-z0-9]+(?:[._-][a-z0-9]+)*@[0-9]+(?:\.[0-9]+){1,2}(?:-[0-9A-Za-z.-]+)?$
          maxLength: 200
    get:
      operationId: getProfileMapping
      tags:
        - Profiles
      summary: Read the immutable requirement-level transparency mapping
      description: |
        Returns official identifiers, original short paraphrases, automation
        classifications, rule mappings, limitations, applicability conditions,
        denominator totals, source provenance, and independent-review status.
        A pending mapping is not standards certification or legal approval.
      responses:
        '200':
          description: Exact immutable mapping artifact including its SHA-256 digest.
          headers:
            ETag:
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/normative-mapping-v1.schema'
        '404':
          $ref: '#/components/responses/NotFound'
  /v1/profiles/{profile_id}/mapping.xlsx:
    parameters:
      - name: profile_id
        in: path
        required: true
        description: Concrete immutable profile identifier and version. `latest` is not accepted for artifact retrieval.
        schema:
          type: string
          pattern: ^[a-z0-9]+(?:[._-][a-z0-9]+)*@[0-9]+(?:\.[0-9]+){1,2}(?:-[0-9A-Za-z.-]+)?$
          maxLength: 200
    get:
      operationId: downloadProfileMappingExcel
      tags:
        - Profiles
      summary: Download the immutable profile transparency mapping as Excel
      responses:
        '200':
          description: Macro-free workbook with Read me, Policy coverage, and Sources worksheets.
          headers:
            ETag:
              schema:
                type: string
            Content-Disposition:
              schema:
                type: string
          content:
            application/vnd.openxmlformats-officedocument.spreadsheetml.sheet:
              schema:
                type: string
                format: binary
        '404':
          $ref: '#/components/responses/NotFound'
  /v1/rules:
    get:
      operationId: listRules
      tags:
        - Profiles
      summary: List immutable rule metadata for rule-pack configuration
      parameters:
        - name: asset_type
          in: query
          schema:
            type: string
            enum:
              - pdf
              - docx
              - url
              - html
      responses:
        '200':
          description: Source-controlled normalized rule registry.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RuleMetadataPage'
        '403':
          $ref: '#/components/responses/Forbidden'
  /v1/rule-packs:
    get:
      operationId: listRulePacks
      tags:
        - Rule packs
      summary: List latest rule-pack versions for a project
      parameters:
        - $ref: '#/components/parameters/ProjectIDQuery'
      responses:
        '200':
          description: Latest immutable version of every project rule pack.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RulePackPage'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /v1/rule-packs/{rule_pack_id}:
    parameters:
      - name: rule_pack_id
        in: path
        required: true
        schema:
          $ref: '#/components/schemas/RulePackIDValue'
    get:
      operationId: getRulePack
      tags:
        - Rule packs
      summary: Read a concrete rule-pack version
      parameters:
        - name: version
          in: query
          schema:
            type: integer
            minimum: 1
      responses:
        '200':
          description: Rule-pack version.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RulePack'
        '404':
          $ref: '#/components/responses/NotFound'
  /v1/suppressions:
    get:
      operationId: listSuppressions
      tags:
        - Suppressions
      summary: List project suppression policies
      description: |
        Returns active suppressions by default. Owner, admin, developer, and
        viewer sessions may list suppression metadata. Project API keys require
        rule-packs:read. Billing-only members cannot read source-facing policy.
      parameters:
        - $ref: '#/components/parameters/ProjectIDQuery'
        - $ref: '#/components/parameters/Cursor'
        - $ref: '#/components/parameters/Limit'
        - name: state
          in: query
          schema:
            type: string
            enum:
              - active
              - expired
              - revoked
              - all
            default: active
      responses:
        '200':
          description: Reverse-chronological suppression page.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuppressionPage'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/ValidationError'
  /v1/webhook-endpoints:
    get:
      operationId: listWebhookEndpoints
      tags:
        - Webhooks
      summary: List webhook endpoints
      parameters:
        - $ref: '#/components/parameters/ProjectIDQuery'
      responses:
        '200':
          description: Endpoint metadata without secrets.
          content:
            application/json:
              schema:
                type: object
                additionalProperties: false
                required:
                  - data
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/WebhookEndpoint'
    post:
      operationId: createWebhookEndpoint
      tags:
        - Webhooks
      summary: Register a signed webhook endpoint
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateWebhookEndpointRequest'
      responses:
        '201':
          description: Endpoint and one-time signing secret.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebhookEndpointCreated'
        '403':
          $ref: '#/components/responses/Forbidden'
        '422':
          $ref: '#/components/responses/ValidationError'
  /v1/webhook-endpoints/{endpoint_id}:
    parameters:
      - $ref: '#/components/parameters/WebhookEndpointID'
    delete:
      operationId: disableWebhookEndpoint
      tags:
        - Webhooks
      summary: Disable an endpoint without deleting its delivery history
      responses:
        '204':
          description: Endpoint disabled; pending deliveries will not be sent.
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /v1/webhook-endpoints/{endpoint_id}/enable:
    parameters:
      - $ref: '#/components/parameters/WebhookEndpointID'
    post:
      operationId: enableWebhookEndpoint
      tags:
        - Webhooks
      summary: Re-enable a disabled endpoint
      description: |
        Restores delivery for an existing endpoint without rotating its signing
        secret or removing its delivery history.
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '200':
          description: Endpoint enabled.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebhookEndpoint'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /v1/webhook-endpoints/{endpoint_id}/rotate-secret:
    parameters:
      - $ref: '#/components/parameters/WebhookEndpointID'
    post:
      operationId: rotateWebhookEndpointSecret
      tags:
        - Webhooks
      summary: Rotate an endpoint signing secret
      description: |
        Returns the new secret exactly once. The previous secret remains valid
        only through previous_secret_expires_at to permit a bounded rollout.
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '200':
          description: New one-time signing secret and overlap deadline.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebhookSecretRotation'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
  /v1/webhook-endpoints/{endpoint_id}/test:
    parameters:
      - $ref: '#/components/parameters/WebhookEndpointID'
    post:
      operationId: testWebhookEndpoint
      tags:
        - Webhooks
      summary: Queue a signed test delivery
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '202':
          description: Test delivery queued.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebhookDelivery'
        '404':
          $ref: '#/components/responses/NotFound'
  /v1/webhook-deliveries:
    get:
      operationId: listWebhookDeliveries
      tags:
        - Webhooks
      summary: List webhook delivery attempts
      parameters:
        - $ref: '#/components/parameters/ProjectIDQuery'
        - $ref: '#/components/parameters/Cursor'
        - $ref: '#/components/parameters/Limit'
      responses:
        '200':
          description: Delivery page.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebhookDeliveryPage'
  /v1/webhook-deliveries/{delivery_id}/replay:
    parameters:
      - $ref: '#/components/parameters/WebhookDeliveryID'
    post:
      operationId: replayWebhookDelivery
      tags:
        - Webhooks
      summary: Queue a new delivery generation for an existing event
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '202':
          description: Replay generation queued for at-least-once delivery.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebhookDelivery'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
  /v1/usage:
    get:
      operationId: getUsage
      tags:
        - Usage
      summary: Read credits for an entitlement period
      description: |
        Owner, viewer, or billing session role is required. Project API keys
        require the usage:read scope.
      parameters:
        - $ref: '#/components/parameters/OrganizationIDQuery'
        - name: period
          in: query
          description: Calendar month in UTC.
          schema:
            type: string
            pattern: ^[0-9]{4}-(0[1-9]|1[0-2])$
      responses:
        '200':
          description: Usage totals and component breakdown.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Usage'
        '403':
          $ref: '#/components/responses/Forbidden'
  /.well-known/accesspreflight-evidence-keys.json:
    get:
      operationId: getEvidenceVerificationKeys
      tags:
        - Evidence
      security: []
      summary: Discover public Ed25519 keys for evidence-manifest verification
      description: |
        Keys use the standard OKP JWK representation. Retired verification
        keys remain published for at least the maximum evidence retention
        period so historical signatures stay verifiable.
      responses:
        '200':
          description: Current and retained evidence-verification keys.
          headers:
            Cache-Control:
              description: Public cache policy; clients must periodically revalidate for rotations.
              schema:
                type: string
                example: public, max-age=3600, must-revalidate
          content:
            application/jwk-set+json:
              schema:
                $ref: '#/components/schemas/EvidenceJWKSet'
webhooks:
  accessPreflightEvent:
    post:
      operationId: receiveAccessPreflightWebhookEvent
      summary: Receive a signed AccessPreflight event
      description: |
        Verify AccessPreflight-Signature over
        `<timestamp>.<exact raw request body>`, reject timestamps outside the
        configured tolerance, and deduplicate by AccessPreflight-Event-ID.
        Delivery is at least once with exponential retries for at least 24
        hours. Payloads contain a summary; fetch the full result through the
        authenticated API. Return any 2xx status only after durably accepting
        the event.
      security: []
      parameters:
        - $ref: '#/components/parameters/AccessPreflightSignature'
        - $ref: '#/components/parameters/AccessPreflightEventID'
        - $ref: '#/components/parameters/AccessPreflightDeliveryID'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/WebhookEventEnvelope'
            examples:
              scanCompleted:
                summary: Completed scan event
                value:
                  id: evt_018f7e8a7d7b7c008000000000000060
                  type: scan.completed
                  created_at: '2026-07-28T21:02:14Z'
                  data:
                    scan_id: scn_018f7e8a7d7b7c008000000000000002
                    status: completed
                    result: detected_failures
                    risk_score: 72
                    quality_gate_status: failed
                    summary:
                      blocker: 0
                      critical: 1
                      major: 2
                      minor: 0
                      info: 0
                      manual_checks_required: 3
                    retrieval_url: /v1/scans/scn_018f7e8a7d7b7c008000000000000002
      responses:
        2XX:
          description: Event durably accepted, including a duplicate already accepted.
        default:
          description: Delivery failed and will be retried according to the documented retry policy.
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: ap_live_ or ap_test_ project API key
      description: Send the complete project API key issued in the AccessPreflight console. Account and organization sessions are not part of this integration API.
  parameters:
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: true
      description: |
        Unique per principal, method, and route for at least 24 hours. Reuse
        with a different request body returns idempotency_key_reused.
      schema:
        type: string
        minLength: 1
        maxLength: 255
        pattern: ^[\x21-\x7E]+$
    Cursor:
      name: cursor
      in: query
      schema:
        type: string
        minLength: 1
        maxLength: 512
    Limit:
      name: limit
      in: query
      schema:
        type: integer
        minimum: 1
        maximum: 100
        default: 25
    OrganizationIDQuery:
      name: organization_id
      in: query
      required: true
      schema:
        $ref: '#/components/schemas/OrganizationIDValue'
    ProjectIDQuery:
      name: project_id
      in: query
      required: true
      schema:
        $ref: '#/components/schemas/ProjectIDValue'
    UploadID:
      name: upload_id
      in: path
      required: true
      schema:
        $ref: '#/components/schemas/UploadIDValue'
    ScanID:
      name: scan_id
      in: path
      required: true
      schema:
        $ref: '#/components/schemas/ScanIDValue'
    WebhookEndpointID:
      name: endpoint_id
      in: path
      required: true
      schema:
        $ref: '#/components/schemas/WebhookEndpointIDValue'
    WebhookDeliveryID:
      name: delivery_id
      in: path
      required: true
      schema:
        $ref: '#/components/schemas/WebhookDeliveryIDValue'
    AccessPreflightSignature:
      name: AccessPreflight-Signature
      in: header
      required: true
      description: One or two HMAC-SHA-256 signatures of `<timestamp>.<exact raw request body>`. During a secret-rotation overlap the header includes both the current and retiring `v1` signatures; verifiers must accept the event when any `v1` value is valid for the supplied timestamp and raw body.
      schema:
        type: string
        pattern: ^t=[0-9]{10,},v1=[a-f0-9]{64}(,v1=[a-f0-9]{64})?$
        example: t=1785272712,v1=0f0e0d0c0b0a090807060504030201000f0e0d0c0b0a09080706050403020100
    AccessPreflightEventID:
      name: AccessPreflight-Event-ID
      in: header
      required: true
      schema:
        $ref: '#/components/schemas/WebhookEventIDValue'
    AccessPreflightDeliveryID:
      name: AccessPreflight-Delivery-ID
      in: header
      required: true
      schema:
        $ref: '#/components/schemas/WebhookDeliveryIDValue'
  headers:
    RequestID:
      description: Stable request correlation identifier.
      schema:
        $ref: '#/components/schemas/RequestIDValue'
  responses:
    Forbidden:
      description: The authenticated principal lacks the required tenant permission.
      headers:
        X-Request-ID:
          $ref: '#/components/headers/RequestID'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    NotFound:
      description: The tenant-scoped resource does not exist or is not visible.
      headers:
        X-Request-ID:
          $ref: '#/components/headers/RequestID'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    Conflict:
      description: The request conflicts with existing state or idempotency.
      headers:
        X-Request-ID:
          $ref: '#/components/headers/RequestID'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    ValidationError:
      description: The request is syntactically valid JSON but violates its contract.
      headers:
        X-Request-ID:
          $ref: '#/components/headers/RequestID'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    TooManyRequests:
      description: A rate, quota, or concurrency limit was reached.
      headers:
        X-Request-ID:
          $ref: '#/components/headers/RequestID'
        Retry-After:
          schema:
            type: integer
            minimum: 1
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
  schemas:
    RequestIDValue:
      type: string
      description: Public request identifier backed by an application-generated UUIDv7.
      pattern: ^req_[a-f0-9]{12}7[a-f0-9]{3}[89ab][a-f0-9]{15}$
    UserIDValue:
      type: string
      description: Public user identifier backed by an application-generated UUIDv7.
      pattern: ^usr_[a-f0-9]{12}7[a-f0-9]{3}[89ab][a-f0-9]{15}$
    OrganizationIDValue:
      type: string
      description: Public organization identifier backed by an application-generated UUIDv7.
      pattern: ^org_[a-f0-9]{12}7[a-f0-9]{3}[89ab][a-f0-9]{15}$
    ProjectIDValue:
      type: string
      description: Public project identifier backed by an application-generated UUIDv7.
      pattern: ^prj_[a-f0-9]{12}7[a-f0-9]{3}[89ab][a-f0-9]{15}$
    UploadIDValue:
      type: string
      description: Public upload identifier backed by an application-generated UUIDv7.
      pattern: ^upl_[a-f0-9]{12}7[a-f0-9]{3}[89ab][a-f0-9]{15}$
    AssetIDValue:
      type: string
      description: Public asset identifier backed by an application-generated UUIDv7.
      pattern: ^ast_[a-f0-9]{12}7[a-f0-9]{3}[89ab][a-f0-9]{15}$
    ScanIDValue:
      type: string
      description: Public scan identifier backed by an application-generated UUIDv7.
      pattern: ^scn_[a-f0-9]{12}7[a-f0-9]{3}[89ab][a-f0-9]{15}$
    RulePackIDValue:
      type: string
      description: Public rule-pack identifier backed by an application-generated UUIDv7.
      pattern: ^rpk_[a-f0-9]{12}7[a-f0-9]{3}[89ab][a-f0-9]{15}$
    SuppressionIDValue:
      type: string
      description: Public suppression identifier backed by an application-generated UUIDv7.
      pattern: ^sup_[a-f0-9]{12}7[a-f0-9]{3}[89ab][a-f0-9]{15}$
    WebhookEndpointIDValue:
      type: string
      description: Public webhook-endpoint identifier backed by an application-generated UUIDv7.
      pattern: ^whe_[a-f0-9]{12}7[a-f0-9]{3}[89ab][a-f0-9]{15}$
    WebhookEventIDValue:
      type: string
      description: Public webhook-event identifier backed by an application-generated UUIDv7.
      pattern: ^evt_[a-f0-9]{12}7[a-f0-9]{3}[89ab][a-f0-9]{15}$
    WebhookDeliveryIDValue:
      type: string
      description: Public webhook-delivery identifier backed by an application-generated UUIDv7.
      pattern: ^whd_[a-f0-9]{12}7[a-f0-9]{3}[89ab][a-f0-9]{15}$
    DeletionIDValue:
      type: string
      description: Public deletion-request identifier backed by an application-generated UUIDv7.
      pattern: ^del_[a-f0-9]{12}7[a-f0-9]{3}[89ab][a-f0-9]{15}$
    CreditGrantIDValue:
      type: string
      description: Public credit-grant identifier backed by an application-generated UUIDv7.
      pattern: ^cgr_[a-f0-9]{12}7[a-f0-9]{3}[89ab][a-f0-9]{15}$
    DeletableResourceIDValue:
      oneOf:
        - $ref: '#/components/schemas/UploadIDValue'
        - $ref: '#/components/schemas/AssetIDValue'
        - $ref: '#/components/schemas/ScanIDValue'
        - $ref: '#/components/schemas/ProjectIDValue'
        - $ref: '#/components/schemas/OrganizationIDValue'
    ErrorResponse:
      type: object
      additionalProperties: false
      required:
        - error
      properties:
        error:
          type: object
          additionalProperties: false
          required:
            - code
            - message
            - request_id
          properties:
            code:
              type: string
              enum:
                - invalid_request
                - unauthenticated
                - forbidden
                - not_found
                - conflict
                - idempotency_key_reused
                - idempotency_in_progress
                - idempotency_state_conflict
                - unsupported_media_type
                - asset_too_large
                - asset_size_mismatch
                - asset_not_ready
                - body_too_large
                - checksum_mismatch
                - content_digest_mismatch
                - content_length_required
                - page_limit_exceeded
                - password_required
                - malformed_document
                - malware_detected
                - malware_scan_pending
                - malware_scan_failed
                - malware_scanner_unavailable
                - upload_incomplete
                - upload_changed
                - upload_content_already_stored
                - upload_not_completable
                - upload_not_ready
                - upload_not_writable
                - upload_state_conflict
                - upload_artifact_state_conflict
                - source_artifact_state_conflict
                - source_expired
                - fetch_target_blocked
                - fetch_failed
                - scan_timeout
                - engine_unavailable
                - profile_not_supported
                - registry_unavailable
                - rule_not_supported
                - baseline_incompatible
                - scan_not_completed
                - scan_state_conflict
                - quota_exceeded
                - rate_limited
                - deletion_in_progress
                - retention_policy_not_allowed
                - storage_integrity_metadata_unavailable
                - storage_unavailable
                - service_unavailable
                - report_artifact_state_conflict
                - subscription_inactive
                - webhook_target_blocked
            message:
              type: string
              minLength: 1
              maxLength: 500
            request_id:
              $ref: '#/components/schemas/RequestIDValue'
            details:
              type: object
              maxProperties: 30
              additionalProperties: true
    ProjectEnvironment:
      type: string
      enum:
        - test
        - staging
        - production
    SourceRetention:
      type: string
      enum:
        - 0h
        - 24h
        - 7d
        - 30d
    CreateUploadRequest:
      type: object
      additionalProperties: false
      required:
        - project_id
        - environment
        - file_name
        - declared_media_type
        - size_bytes
        - sha256
        - retention
      properties:
        project_id:
          $ref: '#/components/schemas/ProjectIDValue'
        environment:
          $ref: '#/components/schemas/ProjectEnvironment'
        file_name:
          type: string
          minLength: 1
          maxLength: 255
        declared_media_type:
          type: string
          enum:
            - application/pdf
            - application/vnd.openxmlformats-officedocument.wordprocessingml.document
            - text/html
            - application/zip
        size_bytes:
          type: integer
          minimum: 1
          maximum: 52428800
        sha256:
          type: string
          pattern: ^[a-f0-9]{64}$
        retention:
          $ref: '#/components/schemas/SourceRetention'
    Upload:
      type: object
      additionalProperties: false
      required:
        - id
        - project_id
        - environment
        - status
        - declared_media_type
        - size_bytes
        - sha256
        - created_at
      properties:
        id:
          $ref: '#/components/schemas/UploadIDValue'
        project_id:
          $ref: '#/components/schemas/ProjectIDValue'
        environment:
          $ref: '#/components/schemas/ProjectEnvironment'
        status:
          type: string
          enum:
            - awaiting_upload
            - uploading
            - uploaded
            - validating
            - ready
            - rejected
            - expired
            - deleting
            - deleted
        declared_media_type:
          type: string
          maxLength: 200
        detected_media_type:
          type:
            - string
            - 'null'
          maxLength: 200
        size_bytes:
          type: integer
          minimum: 1
          maximum: 52428800
        sha256:
          type: string
          pattern: ^[a-f0-9]{64}$
        page_count:
          type:
            - integer
            - 'null'
          minimum: 1
          maximum: 500
          description: |
            Null until an isolated document worker supplies an authoritative
            count. PDF intake does not estimate pages in the API process.
        page_count_basis:
          type:
            - string
            - 'null'
          enum:
            - pdf_parser
            - docx_structural_estimate_v1
            - null
          description: |
            Null until page_count is available. PDF counts use pdf_parser only
            after PDFBox analysis; DOCX counts are structural estimates.
        upload:
          oneOf:
            - $ref: '#/components/schemas/UploadTarget'
            - type: 'null'
        rejection:
          oneOf:
            - $ref: '#/components/schemas/SafeFailure'
            - type: 'null'
        source_retained_until:
          type:
            - string
            - 'null'
          format: date-time
        created_at:
          type: string
          format: date-time
    UploadTarget:
      type: object
      additionalProperties: false
      required:
        - method
        - url
        - expires_at
        - required_headers
      properties:
        method:
          const: PUT
        url:
          type: string
          format: uri
          maxLength: 8192
          description: Authenticated AccessPreflight API endpoint; never an object-store URL.
        expires_at:
          type: string
          format: date-time
        required_headers:
          type: object
          additionalProperties:
            type: string
            maxLength: 2048
    CreateScanRequest:
      type: object
      additionalProperties: false
      required:
        - project_id
        - environment
        - asset
        - profiles
      properties:
        project_id:
          $ref: '#/components/schemas/ProjectIDValue'
        environment:
          $ref: '#/components/schemas/ProjectEnvironment'
        asset:
          oneOf:
            - $ref: '#/components/schemas/UploadAssetInput'
            - $ref: '#/components/schemas/URLAssetInput'
            - $ref: '#/components/schemas/HTMLAssetInput'
          discriminator:
            propertyName: type
        profiles:
          type: array
          minItems: 1
          maxItems: 20
          uniqueItems: true
          items:
            $ref: '#/components/schemas/ProfileSelector'
        rule_pack_id:
          oneOf:
            - $ref: '#/components/schemas/RulePackIDValue'
            - type: 'null'
        options:
          $ref: '#/components/schemas/ScanOptions'
        quality_gate:
          $ref: '#/components/schemas/QualityGatePolicy'
        legal_context:
          $ref: '#/components/schemas/LegalContextInput'
          description: |
            Optional customer declarations for one selected legal-context
            profile. Omit this object to run the technical scan and report legal
            applicability as not assessed. When supplied, declarations required
            by the targeted profile must be complete. These values frame
            technical evidence; they are not an applicability decision.
        metadata:
          type: object
          maxProperties: 20
          additionalProperties:
            type: string
            maxLength: 500
    UploadAssetInput:
      type: object
      additionalProperties: false
      required:
        - type
        - upload_id
      properties:
        type:
          const: upload
        upload_id:
          $ref: '#/components/schemas/UploadIDValue'
    URLAssetInput:
      type: object
      additionalProperties: false
      required:
        - type
        - url
      properties:
        type:
          const: url
        url:
          type: string
          format: uri
          pattern: ^https?://
          maxLength: 2048
        viewport:
          type: string
          enum:
            - desktop
            - mobile
          default: desktop
        wait:
          $ref: '#/components/schemas/WaitStrategy'
    HTMLAssetInput:
      type: object
      additionalProperties: false
      required:
        - type
      properties:
        type:
          const: html
        upload_id:
          $ref: '#/components/schemas/UploadIDValue'
        html:
          type: string
          minLength: 1
          maxLength: 1048576
      oneOf:
        - required:
            - upload_id
          not:
            required:
              - html
        - required:
            - html
          not:
            required:
              - upload_id
    WaitStrategy:
      type: object
      additionalProperties: false
      required:
        - strategy
        - timeout_ms
      properties:
        strategy:
          type: string
          enum:
            - dom_ready
            - network_idle
            - selector
        selector:
          type: string
          maxLength: 500
        timeout_ms:
          type: integer
          minimum: 1000
          maximum: 30000
      allOf:
        - if:
            properties:
              strategy:
                const: selector
            required:
              - strategy
          then:
            required:
              - selector
    ScanOptions:
      type: object
      additionalProperties: false
      properties:
        include_repair_plan:
          type: boolean
          default: true
        include_screenshots:
          type: boolean
          description: |
            Capture one PNG of the configured URL viewport. This option is
            valid only for URL assets. The organization must have screenshot
            retention enabled; the private artifact is retained for exactly
            24 hours and is excluded from signed evidence.
          default: false
        compare_to_baseline:
          oneOf:
            - $ref: '#/components/schemas/ScanIDValue'
            - type: 'null'
    QualityGatePolicy:
      type: object
      additionalProperties: false
      properties:
        block_on:
          type: array
          uniqueItems: true
          items:
            $ref: '#/components/schemas/Severity'
        block_on_new_only:
          type: boolean
          default: false
        max_major_new:
          type: integer
          minimum: 0
        require_manual_checklist_acknowledgement:
          type: boolean
          default: false
        fail_on_incomplete:
          type: boolean
          description: Fail closed when coverage or execution is incomplete.
          default: true
    LegalContextInput:
      type: object
      additionalProperties: false
      required:
        - jurisdiction
      properties:
        profile:
          allOf:
            - $ref: '#/components/schemas/ProfileSelector'
          description: |
            Legal-context profile targeted by these declarations. It must be one of the effective profiles after any saved rule pack is resolved and match the declared jurisdiction. It is required when more than one selected legal-context profile matches that jurisdiction. Other selected legal profiles remain unassessed.
        jurisdiction:
          type: string
          pattern: ^[A-Z]{2}$
        consumer_facing:
          type: boolean
        service_category:
          type: string
          maxLength: 100
        microenterprise_service_provider:
          type: boolean
        publication_date:
          type: string
          format: date
        public_sector:
          type: boolean
        archive_content:
          type: boolean
        third_party_control:
          type: boolean
        administrative_service:
          type: boolean
        entity_population:
          type: integer
          format: int64
          minimum: 0
        special_district:
          type: boolean
        claimed_exemption:
          type: string
          maxLength: 500
    ProfileSelector:
      type: string
      pattern: ^[a-z0-9]+(?:[._-][a-z0-9]+)*@(latest|[0-9]+(?:\.[0-9]+){1,2}(?:-[0-9A-Za-z.-]+)?)$
      maxLength: 200
    ScanStatus:
      type: string
      enum:
        - created
        - awaiting_upload
        - queued
        - fetching
        - scanning
        - normalizing
        - reporting
        - completed
        - rejected
        - fetch_failed
        - unsupported
        - timed_out
        - engine_failed
        - cancelled
    ScanResult:
      type: string
      enum:
        - detected_failures
        - review_required
        - no_detected_failures
        - incomplete
    Scan:
      type: object
      additionalProperties: false
      required:
        - id
        - project_id
        - sequence_number
        - name
        - environment
        - status
        - resolved_profiles
        - state_transitions
        - created_at
        - updated_at
        - links
      properties:
        id:
          $ref: '#/components/schemas/ScanIDValue'
        project_id:
          $ref: '#/components/schemas/ProjectIDValue'
        sequence_number:
          type: integer
          format: int64
          minimum: 1
        name:
          type: string
          pattern: ^Scan [1-9][0-9]*$
          maxLength: 64
        environment:
          $ref: '#/components/schemas/ProjectEnvironment'
        status:
          $ref: '#/components/schemas/ScanStatus'
        result:
          oneOf:
            - $ref: '#/components/schemas/ScanResult'
            - type: 'null'
        risk_score:
          type:
            - integer
            - 'null'
          minimum: 0
          maximum: 100
        quality_gate:
          oneOf:
            - $ref: '#/components/schemas/QualityGateResult'
            - type: 'null'
        asset:
          oneOf:
            - $ref: '#/components/schemas/ScanAssetSummary'
            - type: 'null'
        summary:
          oneOf:
            - $ref: '#/components/schemas/SeveritySummary'
            - type: 'null'
        coverage:
          oneOf:
            - $ref: '#/components/schemas/coverageSummary'
            - type: 'null'
        resolved_profiles:
          type: array
          minItems: 1
          maxItems: 20
          uniqueItems: true
          items:
            type: string
            maxLength: 200
        engine_versions:
          type: object
          maxProperties: 30
          additionalProperties:
            type: string
            maxLength: 100
        state_transitions:
          type: array
          minItems: 1
          maxItems: 100
          items:
            $ref: '#/components/schemas/ScanStateTransition'
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        completed_at:
          type:
            - string
            - 'null'
          format: date-time
        links:
          type: object
          additionalProperties: false
          required:
            - self
            - findings
            - coverage
          properties:
            self:
              type: string
              format: uri-reference
            findings:
              type: string
              format: uri-reference
            coverage:
              type: string
              format: uri-reference
            report_json:
              type: string
              format: uri-reference
            report_html:
              type: string
              format: uri-reference
            report_xlsx:
              type: string
              format: uri-reference
            report_acr:
              type: string
              format: uri-reference
              description: Present only when the scan includes the US Section 508 ICT profile; this is evidence for human ACR authoring, not a completed ACR or VPAT.
            evidence:
              type: string
              format: uri-reference
        failure:
          oneOf:
            - $ref: '#/components/schemas/SafeFailure'
            - type: 'null'
    ScanStateTransition:
      type: object
      additionalProperties: false
      required:
        - status
        - at
      properties:
        status:
          $ref: '#/components/schemas/ScanStatus'
        at:
          type: string
          format: date-time
        reason_code:
          type:
            - string
            - 'null'
          pattern: ^[a-z][a-z0-9_]{1,99}$
        reason:
          type:
            - string
            - 'null'
          maxLength: 500
          description: Sanitized customer cancellation context, when supplied.
    ScanPage:
      type: object
      additionalProperties: false
      required:
        - data
        - next_cursor
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/Scan'
        next_cursor:
          type:
            - string
            - 'null'
          maxLength: 512
    SafeFailure:
      type: object
      additionalProperties: false
      required:
        - code
        - message
      properties:
        code:
          type: string
          maxLength: 100
        phase:
          type: string
          pattern: ^[a-z][a-z0-9_]{0,99}$
          maxLength: 100
          description: Sanitized worker phase in which the failure occurred.
        message:
          type: string
          maxLength: 500
    ScanAssetSummary:
      type: object
      additionalProperties: false
      required:
        - type
        - sha256
      properties:
        type:
          type: string
          enum:
            - pdf
            - docx
            - url
            - html
        sha256:
          type: string
          pattern: ^[a-f0-9]{64}$
        url:
          type: string
          format: uri
          maxLength: 2048
          description: |
            Normalized customer-submitted target URL for URL scans. This is
            returned only from authenticated tenant-scoped scan endpoints and
            remains encrypted at rest.
        page_count:
          type:
            - integer
            - 'null'
          minimum: 1
          maximum: 500
          description: |
            Exact for PDF only after isolated PDFBox analysis; structurally
            estimated for DOCX; otherwise null.
        page_count_basis:
          type:
            - string
            - 'null'
          enum:
            - pdf_parser
            - docx_structural_estimate_v1
            - not_applicable
            - null
        source_retained_until:
          type:
            - string
            - 'null'
          format: date-time
    Severity:
      type: string
      enum:
        - blocker
        - critical
        - major
        - minor
        - info
    Automation:
      type: string
      enum:
        - deterministic
        - partial
        - heuristic
        - manual
    Confidence:
      type: string
      enum:
        - certain
        - high
        - medium
        - low
        - human_required
    SeveritySummary:
      type: object
      additionalProperties: false
      required:
        - blocker
        - critical
        - major
        - minor
        - info
        - manual_checks_required
      properties:
        blocker:
          type: integer
          minimum: 0
        critical:
          type: integer
          minimum: 0
        major:
          type: integer
          minimum: 0
        minor:
          type: integer
          minimum: 0
        info:
          type: integer
          minimum: 0
        manual_checks_required:
          type: integer
          minimum: 0
    QualityGateResult:
      type: object
      additionalProperties: false
      required:
        - status
        - reasons
      properties:
        status:
          type: string
          enum:
            - passed
            - failed
            - indeterminate
            - not_configured
        reasons:
          type: array
          maxItems: 100
          items:
            type: string
            maxLength: 500
    FindingPage:
      type: object
      additionalProperties: false
      required:
        - data
        - next_cursor
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/finding'
        next_cursor:
          type:
            - string
            - 'null'
          maxLength: 512
    CoverageResponse:
      type: object
      additionalProperties: false
      required:
        - summary
        - items
        - reduced_reasons
      properties:
        summary:
          $ref: '#/components/schemas/coverageSummary'
        items:
          type: array
          maxItems: 5000
          items:
            type: object
            additionalProperties: false
            required:
              - rule_id
              - automation
              - outcome
            properties:
              rule_id:
                type: string
                maxLength: 200
              automation:
                $ref: '#/components/schemas/Automation'
              outcome:
                type: string
                enum:
                  - passed
                  - failed
                  - not_applicable
                  - reduced_coverage
                  - not_executed
                  - manual
              reason:
                type: string
                maxLength: 500
        reduced_reasons:
          type: array
          maxItems: 5000
          uniqueItems: true
          items:
            type: string
            minLength: 1
            maxLength: 500
    ScanDiff:
      type: object
      additionalProperties: false
      required:
        - scan_id
        - baseline_scan_id
        - fingerprint_version
        - new
        - recurring
        - resolved
      properties:
        scan_id:
          $ref: '#/components/schemas/ScanIDValue'
        baseline_scan_id:
          $ref: '#/components/schemas/ScanIDValue'
        fingerprint_version:
          type: string
          pattern: ^aps-fingerprint-[1-9][0-9]*$
        new:
          type: array
          items:
            type: string
            maxLength: 256
        recurring:
          type: array
          items:
            type: string
            maxLength: 256
        resolved:
          type: array
          items:
            type: string
            maxLength: 256
    Profile:
      type: object
      additionalProperties: false
      required:
        - id
        - version
        - label
        - asset_types
        - status
        - mapping_status
        - mapping_version
        - profile_kind
        - legal_effect
        - source_urls
        - disclaimer
      properties:
        id:
          type: string
          pattern: ^[a-z0-9]+(?:[._-][a-z0-9]+)*$
          maxLength: 150
        version:
          type: string
          pattern: ^[0-9]+(?:\.[0-9]+){1,2}(?:-[0-9A-Za-z]+(?:[.-][0-9A-Za-z]+)*)?$
          maxLength: 40
        label:
          type: string
          maxLength: 200
        asset_types:
          type: array
          uniqueItems: true
          items:
            type: string
            enum:
              - pdf
              - docx
              - url
              - html
        status:
          type: string
          enum:
            - draft
            - active
            - preview
            - retired
        mapping_status:
          type: string
          enum:
            - pending_review
            - approved
            - beta_reviewed
            - preview
        mapping_version:
          type: string
          maxLength: 100
        profile_kind:
          type: string
          enum:
            - technical
            - technical_preview
            - legal_context
            - national_legal_context
        legal_effect:
          type: string
          enum:
            - none
            - technical_mapping_only
            - customer_scope_aid_only
        default_enabled:
          type: boolean
          default: true
        harmonization_status:
          type: string
          maxLength: 100
        jurisdictions:
          type: array
          uniqueItems: true
          items:
            type: string
            minLength: 2
            maxLength: 30
        normative_targets:
          type: array
          uniqueItems: true
          items:
            type: string
            maxLength: 200
        supplemental_targets:
          type: array
          uniqueItems: true
          items:
            type: string
            maxLength: 200
        scope_summary:
          type: string
          maxLength: 1000
        coverage_summary:
          type: string
          maxLength: 1000
        unsupported_scope:
          type: array
          uniqueItems: true
          items:
            type: string
            maxLength: 300
        required_declarations:
          type: array
          uniqueItems: true
          description: |
            Customer declarations required only when the customer explicitly
            supplies legal context targeting this profile. They never block a
            technical scan when legal_context is omitted.
          items:
            type: string
            enum:
              - administrative_service
              - archive_content
              - claimed_exemption
              - consumer_facing
              - entity_population
              - microenterprise_service_provider
              - public_sector
              - publication_date
              - service_category
              - special_district
              - third_party_control
        effective_from:
          type: string
          format: date
        source_verified_at:
          type: string
          format: date
        review_due_at:
          type: string
          format: date
        specialist_review_status:
          type: string
          enum:
            - required
            - approved
            - preview
        allowed_claim:
          type: string
          maxLength: 500
        prohibited_claims:
          type: array
          uniqueItems: true
          items:
            type: string
            maxLength: 200
        engine_requirement:
          $ref: '#/components/schemas/ProfileEngineRequirement'
        source_urls:
          type: array
          items:
            type: string
            format: uri
            maxLength: 2048
        rules:
          type: array
          items:
            type: string
            maxLength: 200
        disclaimer:
          type: string
          minLength: 1
          maxLength: 1000
        mapping_url:
          type: string
          pattern: ^/v1/profiles/.+/mapping$
        mapping_xlsx_url:
          type: string
          pattern: ^/v1/profiles/.+/mapping\.xlsx$
        mapping_manifest_sha256:
          type: string
          pattern: ^[a-f0-9]{64}$
        mapping_denominator:
          $ref: '#/components/schemas/MappingDenominator'
    MappingDenominator:
      type: object
      additionalProperties: false
      required:
        - total
        - deterministic
        - partial
        - heuristic
        - manual
        - unsupported
        - by_asset_type
      properties:
        total:
          type: integer
          minimum: 0
        deterministic:
          type: integer
          minimum: 0
        partial:
          type: integer
          minimum: 0
        heuristic:
          type: integer
          minimum: 0
        manual:
          type: integer
          minimum: 0
        unsupported:
          type: integer
          minimum: 0
        by_asset_type:
          type: object
          additionalProperties:
            type: integer
            minimum: 0
    ProfileEngineRequirement:
      type: object
      additionalProperties: false
      required:
        - name
        - flavour
        - minimum_version
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 100
        flavour:
          type: string
          minLength: 1
          maxLength: 100
        minimum_version:
          type: string
          minLength: 1
          maxLength: 40
        exact_version:
          type: string
          minLength: 1
          maxLength: 40
    RuleMetadata:
      type: object
      additionalProperties: false
      required:
        - id
        - version
        - title
        - description
        - asset_types
        - family
        - automation
        - default_severity
        - confidence
        - repair_template
      properties:
        id:
          type: string
          minLength: 1
          maxLength: 200
        version:
          type: string
          minLength: 1
          maxLength: 40
        title:
          type: string
          minLength: 1
          maxLength: 300
        description:
          type: string
          maxLength: 3000
        asset_types:
          type: array
          uniqueItems: true
          items:
            type: string
            enum:
              - pdf
              - docx
              - url
              - html
        family:
          type: string
          minLength: 1
          maxLength: 100
        automation:
          $ref: '#/components/schemas/Automation'
        default_severity:
          $ref: '#/components/schemas/Severity'
        confidence:
          $ref: '#/components/schemas/Confidence'
        repair_template:
          type: string
          maxLength: 5000
    RuleMetadataPage:
      type: object
      additionalProperties: false
      required:
        - data
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/RuleMetadata'
    RulePack:
      type: object
      additionalProperties: false
      required:
        - id
        - project_id
        - name
        - profiles
        - version
        - sha256
        - created_at
      properties:
        id:
          $ref: '#/components/schemas/RulePackIDValue'
        project_id:
          $ref: '#/components/schemas/ProjectIDValue'
        name:
          type: string
          minLength: 1
          maxLength: 120
        profiles:
          type: array
          minItems: 1
          uniqueItems: true
          items:
            $ref: '#/components/schemas/ProfileSelector'
        disabled_rules:
          type: array
          uniqueItems: true
          items:
            type: string
            maxLength: 200
        severity_overrides:
          type: object
          maxProperties: 500
          additionalProperties:
            $ref: '#/components/schemas/Severity'
        quality_gate:
          $ref: '#/components/schemas/QualityGatePolicy'
        manual_checks:
          type: array
          maxItems: 100
          items:
            type: object
            additionalProperties: false
            required:
              - id
              - title
              - guidance
            properties:
              id:
                type: string
                maxLength: 200
              title:
                type: string
                maxLength: 200
              guidance:
                type: string
                maxLength: 2000
        version:
          type: integer
          minimum: 1
        sha256:
          type: string
          pattern: ^[a-f0-9]{64}$
        created_at:
          type: string
          format: date-time
    RulePackPage:
      type: object
      additionalProperties: false
      required:
        - data
        - next_cursor
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/RulePack'
        next_cursor:
          type:
            - string
            - 'null'
          maxLength: 512
    SuppressionState:
      type: string
      enum:
        - active
        - expired
        - revoked
    Suppression:
      type: object
      additionalProperties: false
      required:
        - id
        - project_id
        - rule_id
        - locator_pattern
        - asset_fingerprint
        - reason
        - actor_id
        - state
        - expires_at
        - revoked_at
        - revoked_by
        - created_at
      properties:
        id:
          $ref: '#/components/schemas/SuppressionIDValue'
        project_id:
          $ref: '#/components/schemas/ProjectIDValue'
        rule_id:
          type: string
          pattern: ^[a-z0-9]+(?:[._-][a-z0-9]+)*$
          maxLength: 200
        locator_pattern:
          type: string
          maxLength: 1000
        asset_fingerprint:
          type: string
          pattern: ^$|^[a-f0-9]{64}$
        reason:
          type: string
          minLength: 3
          maxLength: 1000
        actor_id:
          $ref: '#/components/schemas/UserIDValue'
        state:
          $ref: '#/components/schemas/SuppressionState'
        expires_at:
          type:
            - string
            - 'null'
          format: date-time
        revoked_at:
          type:
            - string
            - 'null'
          format: date-time
        revoked_by:
          oneOf:
            - $ref: '#/components/schemas/UserIDValue'
            - type: 'null'
        created_at:
          type: string
          format: date-time
    SuppressionPage:
      type: object
      additionalProperties: false
      required:
        - data
        - next_cursor
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/Suppression'
        next_cursor:
          type:
            - string
            - 'null'
          maxLength: 512
    WebhookEventType:
      type: string
      enum:
        - scan.queued
        - scan.started
        - scan.completed
        - scan.failed
        - scan.cancelled
        - usage.threshold_reached
    WebhookPayloadType:
      type: string
      enum:
        - scan.queued
        - scan.started
        - scan.completed
        - scan.failed
        - scan.cancelled
        - usage.threshold_reached
        - webhook.test
    WebhookEventEnvelope:
      type: object
      additionalProperties: false
      required:
        - id
        - type
        - created_at
        - data
      properties:
        id:
          $ref: '#/components/schemas/WebhookEventIDValue'
        type:
          $ref: '#/components/schemas/WebhookPayloadType'
        created_at:
          type: string
          format: date-time
        data:
          oneOf:
            - $ref: '#/components/schemas/ScanWebhookData'
            - $ref: '#/components/schemas/UsageThresholdWebhookData'
            - $ref: '#/components/schemas/TestWebhookData'
      oneOf:
        - properties:
            type:
              const: scan.queued
            data:
              allOf:
                - $ref: '#/components/schemas/ScanWebhookData'
                - properties:
                    status:
                      const: queued
        - properties:
            type:
              const: scan.started
            data:
              allOf:
                - $ref: '#/components/schemas/ScanWebhookData'
                - properties:
                    status:
                      enum:
                        - fetching
                        - scanning
        - properties:
            type:
              const: scan.completed
            data:
              allOf:
                - $ref: '#/components/schemas/ScanWebhookData'
                - required:
                    - result
                    - risk_score
                    - quality_gate_status
                    - summary
                    - retrieval_url
                  properties:
                    status:
                      const: completed
                    result:
                      $ref: '#/components/schemas/ScanResult'
                    risk_score:
                      type: integer
                      minimum: 0
                      maximum: 100
                    quality_gate_status:
                      type: string
                      enum:
                        - passed
                        - failed
                        - indeterminate
                        - not_configured
        - properties:
            type:
              const: scan.failed
            data:
              allOf:
                - $ref: '#/components/schemas/ScanWebhookData'
                - required:
                    - failure
                  properties:
                    status:
                      enum:
                        - rejected
                        - fetch_failed
                        - unsupported
                        - timed_out
                        - engine_failed
                    failure:
                      $ref: '#/components/schemas/SafeFailure'
        - properties:
            type:
              const: scan.cancelled
            data:
              allOf:
                - $ref: '#/components/schemas/ScanWebhookData'
                - properties:
                    status:
                      const: cancelled
        - properties:
            type:
              const: usage.threshold_reached
            data:
              $ref: '#/components/schemas/UsageThresholdWebhookData'
        - properties:
            type:
              const: webhook.test
            data:
              $ref: '#/components/schemas/TestWebhookData'
    ScanWebhookData:
      type: object
      additionalProperties: false
      required:
        - scan_id
        - status
      properties:
        scan_id:
          $ref: '#/components/schemas/ScanIDValue'
        status:
          $ref: '#/components/schemas/ScanStatus'
        result:
          oneOf:
            - $ref: '#/components/schemas/ScanResult'
            - type: 'null'
        risk_score:
          type:
            - integer
            - 'null'
          minimum: 0
          maximum: 100
        quality_gate_status:
          type:
            - string
            - 'null'
          enum:
            - passed
            - failed
            - indeterminate
            - not_configured
            - null
        summary:
          $ref: '#/components/schemas/SeveritySummary'
        retrieval_url:
          type: string
          format: uri-reference
          maxLength: 500
        failure:
          oneOf:
            - $ref: '#/components/schemas/SafeFailure'
            - type: 'null'
    UsageThresholdWebhookData:
      type: object
      additionalProperties: false
      required:
        - period_start
        - period_end
        - threshold
        - consumed_milli
        - allowance_milli
      properties:
        period_start:
          type: string
          format: date-time
        period_end:
          type: string
          format: date-time
        threshold:
          type: integer
          enum:
            - 50
            - 80
            - 100
        consumed_milli:
          type: integer
          minimum: 0
        allowance_milli:
          type: integer
          minimum: 0
    TestWebhookData:
      type: object
      additionalProperties: false
      required:
        - message
      properties:
        message:
          type: string
          minLength: 1
          maxLength: 200
    CreateWebhookEndpointRequest:
      type: object
      additionalProperties: false
      required:
        - project_id
        - url
        - events
      properties:
        project_id:
          $ref: '#/components/schemas/ProjectIDValue'
        url:
          type: string
          format: uri
          pattern: ^https://
          maxLength: 2048
        events:
          type: array
          minItems: 1
          uniqueItems: true
          items:
            $ref: '#/components/schemas/WebhookEventType'
    WebhookEndpoint:
      type: object
      additionalProperties: false
      required:
        - id
        - project_id
        - url
        - events
        - status
        - created_at
      properties:
        id:
          $ref: '#/components/schemas/WebhookEndpointIDValue'
        project_id:
          $ref: '#/components/schemas/ProjectIDValue'
        url:
          type: string
          format: uri
          maxLength: 2048
        events:
          type: array
          minItems: 1
          uniqueItems: true
          items:
            $ref: '#/components/schemas/WebhookEventType'
        status:
          type: string
          enum:
            - active
            - disabled
        created_at:
          type: string
          format: date-time
    WebhookEndpointCreated:
      type: object
      additionalProperties: false
      required:
        - id
        - project_id
        - url
        - events
        - status
        - created_at
        - signing_secret
      properties:
        id:
          $ref: '#/components/schemas/WebhookEndpointIDValue'
        project_id:
          $ref: '#/components/schemas/ProjectIDValue'
        url:
          type: string
          format: uri
          maxLength: 2048
        events:
          type: array
          minItems: 1
          uniqueItems: true
          items:
            $ref: '#/components/schemas/WebhookEventType'
        status:
          type: string
          enum:
            - active
            - disabled
        created_at:
          type: string
          format: date-time
        signing_secret:
          type: string
          pattern: ^whsec_[A-Za-z0-9_-]{43}$
    WebhookSecretRotation:
      type: object
      additionalProperties: false
      required:
        - signing_secret
        - previous_secret_expires_at
      properties:
        signing_secret:
          type: string
          pattern: ^whsec_[A-Za-z0-9_-]{43}$
        previous_secret_expires_at:
          type: string
          format: date-time
    WebhookDelivery:
      type: object
      additionalProperties: false
      required:
        - id
        - event_id
        - endpoint_id
        - event_type
        - status
        - attempt_count
        - created_at
      properties:
        id:
          $ref: '#/components/schemas/WebhookDeliveryIDValue'
        event_id:
          $ref: '#/components/schemas/WebhookEventIDValue'
        endpoint_id:
          $ref: '#/components/schemas/WebhookEndpointIDValue'
        event_type:
          $ref: '#/components/schemas/WebhookPayloadType'
        status:
          type: string
          enum:
            - pending
            - delivered
            - retrying
            - failed
            - cancelled
        attempt_count:
          type: integer
          minimum: 0
        next_attempt_at:
          type:
            - string
            - 'null'
          format: date-time
        response_status:
          type:
            - integer
            - 'null'
          minimum: 100
          maximum: 599
        delivered_at:
          type:
            - string
            - 'null'
          format: date-time
        created_at:
          type: string
          format: date-time
    WebhookDeliveryPage:
      type: object
      additionalProperties: false
      required:
        - data
        - next_cursor
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/WebhookDelivery'
        next_cursor:
          type:
            - string
            - 'null'
          maxLength: 512
    EvidenceJWKSet:
      type: object
      additionalProperties: false
      required:
        - keys
      properties:
        keys:
          type: array
          minItems: 1
          uniqueItems: true
          items:
            type: object
            additionalProperties: false
            required:
              - kty
              - crv
              - x
              - use
              - alg
              - kid
            properties:
              kty:
                const: OKP
              crv:
                const: Ed25519
              x:
                type: string
                pattern: ^[A-Za-z0-9_-]{43}$
                description: Base64url-encoded 32-byte Ed25519 public key without padding.
              use:
                const: sig
              alg:
                const: EdDSA
              kid:
                type: string
                minLength: 1
                maxLength: 100
              status:
                type: string
                enum:
                  - active
                  - retired
              not_before:
                type: string
                format: date-time
              retired_at:
                type:
                  - string
                  - 'null'
                format: date-time
    Usage:
      type: object
      additionalProperties: false
      required:
        - organization_id
        - period_start
        - period_end
        - allowance_milli
        - used_milli
        - remaining_milli
        - breakdown
        - credit_grants
      properties:
        organization_id:
          $ref: '#/components/schemas/OrganizationIDValue'
        period_start:
          type: string
          format: date-time
        period_end:
          type: string
          format: date-time
        allowance_milli:
          type: integer
          minimum: 0
        used_milli:
          type: integer
          minimum: 0
        remaining_milli:
          type: integer
        breakdown:
          type: array
          items:
            type: object
            additionalProperties: false
            required:
              - component
              - units
              - credits_milli
            properties:
              component:
                type: string
                enum:
                  - url
                  - url_extended
                  - html
                  - pdf_page
                  - docx_page_estimate
                  - screenshot
              units:
                type: integer
                minimum: 0
              credits_milli:
                type: integer
                minimum: 0
        credit_grants:
          type: array
          description: Credit grants overlapping the requested billing period.
          items:
            $ref: '#/components/schemas/CreditGrant'
    CreditGrant:
      type: object
      additionalProperties: false
      required:
        - id
        - credits_milli
        - reason
        - valid_from
        - valid_until
        - granted_by
        - created_at
      properties:
        id:
          $ref: '#/components/schemas/CreditGrantIDValue'
        credits_milli:
          type: integer
          minimum: 1
        reason:
          type: string
          minLength: 1
          maxLength: 500
        valid_from:
          type: string
          format: date-time
        valid_until:
          type: string
          format: date-time
        granted_by:
          $ref: '#/components/schemas/UserIDValue'
        created_at:
          type: string
          format: date-time
    DeletionStoreProgress:
      type: object
      additionalProperties: false
      required:
        - status
      properties:
        status:
          type: string
          enum:
            - queued
            - processing
            - completed
            - failed
    DeletionObjectProgress:
      type: object
      additionalProperties: false
      required:
        - status
        - deleted
        - total
      properties:
        status:
          type: string
          enum:
            - queued
            - discovering
            - processing
            - completed
            - failed
        deleted:
          type: integer
          minimum: 0
        total:
          type: integer
          minimum: 0
    DeletionBackupProgress:
      type: object
      additionalProperties: false
      required:
        - status
        - expires_at
      properties:
        status:
          type: string
          enum:
            - pending
            - retained_until
            - failed
        expires_at:
          type:
            - string
            - 'null'
          format: date-time
    DeletionProgress:
      type: object
      additionalProperties: false
      required:
        - database
        - objects
        - tombstone_ledger
        - backups
      properties:
        database:
          $ref: '#/components/schemas/DeletionStoreProgress'
        objects:
          $ref: '#/components/schemas/DeletionObjectProgress'
        tombstone_ledger:
          $ref: '#/components/schemas/DeletionStoreProgress'
        backups:
          $ref: '#/components/schemas/DeletionBackupProgress'
    DeletionRequest:
      type: object
      additionalProperties: false
      required:
        - id
        - organization_id
        - scope
        - target_id
        - reason
        - status
        - progress
        - attempt_count
        - created_at
        - backup_expiry_disclosure
      properties:
        id:
          $ref: '#/components/schemas/DeletionIDValue'
        organization_id:
          $ref: '#/components/schemas/OrganizationIDValue'
        scope:
          type: string
          enum:
            - upload
            - asset
            - scan
            - project
            - organization
        target_id:
          $ref: '#/components/schemas/DeletableResourceIDValue'
        reason:
          type: string
          minLength: 1
          maxLength: 500
        status:
          type: string
          enum:
            - pending
            - running
            - completed
            - partially_failed
        progress:
          $ref: '#/components/schemas/DeletionProgress'
        attempt_count:
          type: integer
          minimum: 0
        next_retry_at:
          type:
            - string
            - 'null'
          format: date-time
        created_at:
          type: string
          format: date-time
        completed_at:
          type:
            - string
            - 'null'
          format: date-time
        backup_expires_at:
          type:
            - string
            - 'null'
          format: date-time
          description: |
            Null until primary deletion completes. Afterwards, this is the
            primary-deletion completion time plus the configured managed
            backup retention.
        backup_expiry_disclosure:
          type: string
          minLength: 1
          maxLength: 1000
    nonNegativeInteger:
      type: integer
      minimum: 0
    coverageSummary:
      type: object
      additionalProperties: false
      required:
        - rules_configured
        - rules_executed
        - rules_not_applicable
        - rules_reduced_coverage
        - manual_checks_required
        - automated_failures
        - heuristic_findings
      properties:
        rules_configured:
          $ref: '#/components/schemas/nonNegativeInteger'
        rules_executed:
          $ref: '#/components/schemas/nonNegativeInteger'
        rules_not_applicable:
          $ref: '#/components/schemas/nonNegativeInteger'
        rules_reduced_coverage:
          $ref: '#/components/schemas/nonNegativeInteger'
        manual_checks_required:
          $ref: '#/components/schemas/nonNegativeInteger'
        automated_failures:
          $ref: '#/components/schemas/nonNegativeInteger'
        heuristic_findings:
          $ref: '#/components/schemas/nonNegativeInteger'
    findingId:
      type: string
      description: Public finding identifier backed by an application-generated UUIDv7.
      pattern: ^fnd_[a-f0-9]{12}7[a-f0-9]{3}[89ab][a-f0-9]{15}$
    assetId:
      type: string
      description: Public asset identifier backed by an application-generated UUIDv7.
      pattern: ^ast_[a-f0-9]{12}7[a-f0-9]{3}[89ab][a-f0-9]{15}$
    ruleReference:
      type: object
      additionalProperties: false
      required:
        - id
        - version
        - native_engine
        - native_rule_id
      properties:
        id:
          type: string
          pattern: ^[a-z0-9]+(?:[._-][a-z0-9]+)*$
          maxLength: 200
        version:
          type: string
          pattern: ^\d+\.\d+\.\d+$
        native_engine:
          type: string
          minLength: 1
          maxLength: 100
        native_rule_id:
          type: string
          minLength: 1
          maxLength: 200
    severity:
      enum:
        - blocker
        - critical
        - major
        - minor
        - info
    evidence:
      type: object
      additionalProperties: false
      required:
        - locator_type
        - locator
      properties:
        locator_type:
          enum:
            - document
            - pdf_catalog
            - pdf_page
            - pdf_object
            - pdf_mcid
            - pdf_annotation
            - dom_selector
            - accessibility_path
            - docx_part
            - docx_paragraph
            - docx_relationship
            - docx_object
        locator:
          type: string
          minLength: 1
          maxLength: 1000
        page:
          type: integer
          minimum: 1
          maximum: 500
        observed:
          type: string
          maxLength: 512
        context:
          type: string
          maxLength: 512
    sha256:
      type: string
      pattern: ^[\da-f]{64}$
    standardMapping:
      type: object
      additionalProperties: false
      required:
        - profile
        - version
        - reference
        - mapping_version
      properties:
        profile:
          type: string
          minLength: 1
          maxLength: 150
        version:
          type: string
          pattern: ^\d+(?:\.\d+){1,2}(?:-[0-9A-Za-z]+(?:[.-][0-9A-Za-z]+)*)?$
          maxLength: 40
        reference:
          type: string
          minLength: 1
          maxLength: 300
        mapping_version:
          type: string
          minLength: 1
          maxLength: 100
        mapping_sha256:
          $ref: '#/components/schemas/sha256'
        source_id:
          type: string
          minLength: 1
          maxLength: 150
        requirement_id:
          type: string
          minLength: 1
          maxLength: 200
        asset_type:
          enum:
            - url
            - html
            - pdf
            - docx
        classification:
          enum:
            - deterministic
            - partial
            - heuristic
            - manual
            - unsupported
        short_label:
          type: string
          minLength: 1
          maxLength: 300
        limitations:
          type: string
          minLength: 1
          maxLength: 1000
    remediation:
      type: object
      additionalProperties: false
      required:
        - summary
        - steps
      properties:
        summary:
          type: string
          minLength: 1
          maxLength: 1000
        steps:
          type: array
          minItems: 1
          maxItems: 20
          items:
            type: string
            minLength: 1
            maxLength: 1000
    actorId:
      type: string
      description: Public user, API-key, or service actor identifier backed by an application-generated UUIDv7.
      pattern: ^(?:usr|key|svc)_[a-f0-9]{12}7[a-f0-9]{3}[89ab][a-f0-9]{15}$
    timestamp:
      type: string
      format: date-time
    suppression:
      type: object
      additionalProperties: false
      required:
        - actor_id
        - reason
        - created_at
      properties:
        actor_id:
          $ref: '#/components/schemas/actorId'
        reason:
          type: string
          minLength: 1
          maxLength: 1000
        created_at:
          $ref: '#/components/schemas/timestamp'
        expires_at:
          oneOf:
            - $ref: '#/components/schemas/timestamp'
            - type: 'null'
    finding:
      type: object
      additionalProperties: false
      required:
        - id
        - asset_id
        - asset_type
        - fingerprint
        - rule
        - outcome
        - severity
        - confidence
        - automation
        - title
        - description
        - scope
        - evidence
        - standards
        - occurrence_count
        - sample_occurrences
        - remediation
        - manual_follow_up
        - baseline_state
        - suppressed
        - first_seen_at
        - last_seen_at
      properties:
        id:
          $ref: '#/components/schemas/findingId'
        asset_id:
          $ref: '#/components/schemas/assetId'
        asset_type:
          enum:
            - pdf
            - docx
            - url
            - html
        fingerprint:
          type: string
          minLength: 16
          maxLength: 256
        fingerprint_version:
          type: string
          pattern: ^aps-fingerprint-[1-9][0-9]*$
        rule:
          $ref: '#/components/schemas/ruleReference'
        outcome:
          enum:
            - failed
            - review
            - manual
        severity:
          $ref: '#/components/schemas/severity'
        confidence:
          enum:
            - certain
            - high
            - medium
            - low
            - human_required
        automation:
          enum:
            - deterministic
            - partial
            - heuristic
            - manual
        title:
          type: string
          minLength: 1
          maxLength: 200
        description:
          type: string
          minLength: 1
          maxLength: 2000
        document_wide_reason:
          type: string
          minLength: 1
          maxLength: 512
          description: Why the finding applies to the document as a whole when no narrower locator is appropriate.
        scope:
          enum:
            - document
            - page
            - element
            - occurrence
        evidence:
          $ref: '#/components/schemas/evidence'
        standards:
          type: array
          minItems: 1
          maxItems: 30
          items:
            $ref: '#/components/schemas/standardMapping'
        occurrence_count:
          type: integer
          minimum: 1
          maximum: 1000000
        sample_occurrences:
          type: array
          minItems: 1
          maxItems: 20
          items:
            $ref: '#/components/schemas/evidence'
        remediation:
          $ref: '#/components/schemas/remediation'
        manual_follow_up:
          type: array
          maxItems: 20
          items:
            type: string
            maxLength: 1000
        baseline_state:
          enum:
            - new
            - recurring
            - not_compared
        suppressed:
          type: boolean
        suppression:
          $ref: '#/components/schemas/suppression'
        first_seen_at:
          $ref: '#/components/schemas/timestamp'
        last_seen_at:
          $ref: '#/components/schemas/timestamp'
      allOf:
        - if:
            properties:
              suppressed:
                const: true
            required:
              - suppressed
          then:
            required:
              - suppression
          else:
            not:
              required:
                - suppression
    fetch:
      type: object
      additionalProperties: false
      required:
        - fetched_at
        - final_url_redacted
        - status_code
      properties:
        fetched_at:
          $ref: '#/components/schemas/timestamp'
        final_url_redacted:
          type: string
          format: uri
          pattern: ^https?://
          maxLength: 2048
        status_code:
          type: integer
          minimum: 100
          maximum: 599
        redirect_count:
          type: integer
          minimum: 0
          maximum: 10
        request_count:
          type: integer
          minimum: 1
          maximum: 1000
        transferred_bytes:
          type: integer
          minimum: 0
          maximum: 104857600
        response_headers:
          type: object
          description: Allowlisted, redacted metadata only.
          maxProperties: 30
          additionalProperties:
            type: string
            maxLength: 2048
        dom_snapshot_sha256:
          $ref: '#/components/schemas/sha256'
    screenshot:
      type: object
      description: Private artifact metadata only. No object key, bucket, source URL, or download capability is exposed.
      additionalProperties: false
      required:
        - media_type
        - size_bytes
        - sha256
        - width
        - height
        - available_until
      properties:
        media_type:
          const: image/png
        size_bytes:
          type: integer
          minimum: 24
          maximum: 10485760
        sha256:
          $ref: '#/components/schemas/sha256'
        width:
          type: integer
          minimum: 320
          maximum: 3840
        height:
          type: integer
          minimum: 320
          maximum: 2160
        available_until:
          $ref: '#/components/schemas/timestamp'
    profileEngineRequirement:
      type: object
      additionalProperties: false
      required:
        - name
        - flavour
        - minimum_version
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 100
        flavour:
          type: string
          minLength: 1
          maxLength: 100
        minimum_version:
          type: string
          minLength: 1
          maxLength: 100
        exact_version:
          type: string
          minLength: 1
          maxLength: 100
    mappingDenominator:
      type: object
      additionalProperties: false
      required:
        - total
        - deterministic
        - partial
        - heuristic
        - manual
        - unsupported
        - by_asset_type
      properties:
        total:
          $ref: '#/components/schemas/nonNegativeInteger'
        deterministic:
          $ref: '#/components/schemas/nonNegativeInteger'
        partial:
          $ref: '#/components/schemas/nonNegativeInteger'
        heuristic:
          $ref: '#/components/schemas/nonNegativeInteger'
        manual:
          $ref: '#/components/schemas/nonNegativeInteger'
        unsupported:
          $ref: '#/components/schemas/nonNegativeInteger'
        by_asset_type:
          type: object
          additionalProperties:
            type: integer
            minimum: 0
    date:
      type: string
      format: date
    normativeSource:
      type: object
      additionalProperties: false
      required:
        - id
        - title
        - edition
        - kind
        - official_url
        - verified_at
      properties:
        id:
          type: string
          minLength: 1
          maxLength: 150
        title:
          type: string
          minLength: 1
          maxLength: 300
        edition:
          type: string
          minLength: 1
          maxLength: 100
        kind:
          enum:
            - normative
            - machine_profile
            - legal
            - supplemental
        official_url:
          type: string
          format: uri
          pattern: ^https://
        verified_at:
          $ref: '#/components/schemas/date'
    applicabilityCondition:
      type: object
      additionalProperties: false
      required:
        - declaration
        - operator
        - value
      properties:
        declaration:
          type: string
          minLength: 1
          maxLength: 100
        operator:
          enum:
            - equals
            - not_equals
            - present
        value: {}
    rulePackId:
      type: string
      description: Public rule-pack identifier backed by an application-generated UUIDv7.
      pattern: ^rpk_[a-f0-9]{12}7[a-f0-9]{3}[89ab][a-f0-9]{15}$
    scanId:
      type: string
      description: Public scan identifier backed by an application-generated UUIDv7.
      pattern: ^scn_[a-f0-9]{12}7[a-f0-9]{3}[89ab][a-f0-9]{15}$
    reportId:
      type: string
      description: Public report identifier backed by an application-generated UUIDv7.
      pattern: ^rpt_[a-f0-9]{12}7[a-f0-9]{3}[89ab][a-f0-9]{15}$
    organizationId:
      type: string
      description: Public organization identifier backed by an application-generated UUIDv7.
      pattern: ^org_[a-f0-9]{12}7[a-f0-9]{3}[89ab][a-f0-9]{15}$
    projectId:
      type: string
      description: Public project identifier backed by an application-generated UUIDv7.
      pattern: ^prj_[a-f0-9]{12}7[a-f0-9]{3}[89ab][a-f0-9]{15}$
    qualityGate:
      type: object
      additionalProperties: false
      required:
        - status
        - reasons
      properties:
        status:
          enum:
            - passed
            - failed
            - indeterminate
            - not_configured
        reasons:
          type: array
          maxItems: 100
          items:
            type: string
            maxLength: 500
        policy:
          type: object
          additionalProperties: false
          properties:
            block_on:
              type: array
              uniqueItems: true
              items:
                $ref: '#/components/schemas/severity'
            block_on_new_only:
              type: boolean
            max_major_new:
              type: integer
              minimum: 0
            require_manual_checklist_acknowledgement:
              type: boolean
            fail_on_incomplete:
              type: boolean
    asset:
      type: object
      additionalProperties: false
      required:
        - id
        - type
        - sha256
        - size_bytes
        - media_type
      properties:
        id:
          $ref: '#/components/schemas/assetId'
        type:
          enum:
            - pdf
            - docx
            - url
            - html
        sha256:
          $ref: '#/components/schemas/sha256'
        size_bytes:
          type: integer
          minimum: 0
          maximum: 104857600
        media_type:
          type: string
          minLength: 1
          maxLength: 200
        page_count:
          type: integer
          minimum: 1
          maximum: 500
        page_count_basis:
          enum:
            - pdf_parser
            - docx_structural_estimate_v1
            - not_applicable
        source_retained_until:
          oneOf:
            - $ref: '#/components/schemas/timestamp'
            - type: 'null'
        source_reference_redacted:
          type: string
          maxLength: 2048
        fetch:
          $ref: '#/components/schemas/fetch'
        screenshot:
          $ref: '#/components/schemas/screenshot'
    summary:
      type: object
      additionalProperties: false
      required:
        - blocker
        - critical
        - major
        - minor
        - info
        - manual_checks_required
      properties:
        blocker:
          $ref: '#/components/schemas/nonNegativeInteger'
        critical:
          $ref: '#/components/schemas/nonNegativeInteger'
        major:
          $ref: '#/components/schemas/nonNegativeInteger'
        minor:
          $ref: '#/components/schemas/nonNegativeInteger'
        info:
          $ref: '#/components/schemas/nonNegativeInteger'
        manual_checks_required:
          $ref: '#/components/schemas/nonNegativeInteger'
    coverageItem:
      type: object
      additionalProperties: false
      required:
        - rule_id
        - automation
        - outcome
      properties:
        rule_id:
          type: string
          minLength: 1
          maxLength: 200
        automation:
          enum:
            - deterministic
            - partial
            - heuristic
            - manual
        outcome:
          enum:
            - passed
            - failed
            - not_applicable
            - reduced_coverage
            - not_executed
            - manual
        reason:
          type: string
          minLength: 1
          maxLength: 500
    resolvedProfile:
      type: object
      additionalProperties: false
      required:
        - id
        - version
        - label
        - mapping_version
        - mapping_status
      properties:
        id:
          type: string
          pattern: ^[a-z0-9]+(?:[._-][a-z0-9]+)*$
          maxLength: 150
        version:
          type: string
          pattern: ^\d+(?:\.\d+){1,2}(?:-[0-9A-Za-z]+(?:[.-][0-9A-Za-z]+)*)?$
          maxLength: 40
        label:
          type: string
          minLength: 1
          maxLength: 200
        mapping_version:
          type: string
          minLength: 1
          maxLength: 100
        mapping_status:
          enum:
            - pending_review
            - approved
            - beta_reviewed
            - preview
        profile_kind:
          type: string
          minLength: 1
          maxLength: 100
        legal_effect:
          type: string
          minLength: 1
          maxLength: 100
        harmonization_status:
          type: string
          minLength: 1
          maxLength: 160
        scope_summary:
          type: string
          minLength: 1
          maxLength: 2000
        coverage_summary:
          type: string
          minLength: 1
          maxLength: 2000
        unsupported_scope:
          type: array
          maxItems: 30
          uniqueItems: true
          items:
            type: string
            minLength: 1
            maxLength: 500
        required_declarations:
          type: array
          maxItems: 20
          uniqueItems: true
          items:
            type: string
            enum:
              - administrative_service
              - archive_content
              - claimed_exemption
              - consumer_facing
              - entity_population
              - microenterprise_service_provider
              - public_sector
              - publication_date
              - service_category
              - special_district
              - third_party_control
        specialist_review_status:
          enum:
            - required
            - approved
            - preview
        allowed_claim:
          type: string
          minLength: 1
          maxLength: 1000
        prohibited_claims:
          type: array
          maxItems: 30
          uniqueItems: true
          items:
            type: string
            minLength: 1
            maxLength: 300
        engine_requirement:
          $ref: '#/components/schemas/profileEngineRequirement'
        disclaimer:
          type: string
          minLength: 40
          maxLength: 3000
        source_urls:
          type: array
          maxItems: 20
          items:
            type: string
            format: uri
            maxLength: 2048
        manifest_sha256:
          $ref: '#/components/schemas/sha256'
        rules_manifest_sha256:
          $ref: '#/components/schemas/sha256'
        mapping_manifest_sha256:
          $ref: '#/components/schemas/sha256'
        mapping_denominator:
          $ref: '#/components/schemas/mappingDenominator'
        mapping_approval_status:
          enum:
            - not_obtained
            - approved
    requirementCoverage:
      type: object
      additionalProperties: false
      required:
        - profile_id
        - profile_version
        - mapping_version
        - mapping_manifest_sha256
        - mapping_approval_status
        - source
        - requirement_id
        - short_label
        - asset_type
        - classification
        - implemented_rule_ids
        - outcome
        - rationale
        - limitations
      properties:
        profile_id:
          type: string
          minLength: 1
          maxLength: 150
        profile_version:
          type: string
          minLength: 1
          maxLength: 40
        mapping_version:
          type: string
          minLength: 1
          maxLength: 100
        mapping_manifest_sha256:
          $ref: '#/components/schemas/sha256'
        mapping_approval_status:
          enum:
            - not_obtained
            - approved
        source:
          $ref: '#/components/schemas/normativeSource'
        requirement_id:
          type: string
          minLength: 1
          maxLength: 200
        short_label:
          type: string
          minLength: 1
          maxLength: 300
        asset_type:
          enum:
            - url
            - html
            - pdf
            - docx
        classification:
          enum:
            - deterministic
            - partial
            - heuristic
            - manual
            - unsupported
        implemented_rule_ids:
          type: array
          uniqueItems: true
          items:
            type: string
            minLength: 1
            maxLength: 200
        applicability_condition:
          $ref: '#/components/schemas/applicabilityCondition'
        outcome:
          enum:
            - failed
            - passed
            - no_detected_failure
            - review_required
            - manual
            - unsupported
            - not_assessed
            - not_applicable
            - reduced_coverage
            - not_executed
        rationale:
          type: string
          minLength: 1
          maxLength: 1000
        limitations:
          type: string
          minLength: 1
          maxLength: 1000
    resolvedRulePack:
      type: object
      additionalProperties: false
      required:
        - id
        - version
      properties:
        id:
          $ref: '#/components/schemas/rulePackId'
        version:
          type: integer
          minimum: 1
        sha256:
          $ref: '#/components/schemas/sha256'
    manualCheck:
      type: object
      additionalProperties: false
      required:
        - id
        - title
        - guidance
        - status
      properties:
        id:
          type: string
          pattern: ^[a-z0-9]+(?:[._-][a-z0-9]+)*$
          maxLength: 200
        title:
          type: string
          minLength: 1
          maxLength: 200
        guidance:
          type: string
          minLength: 1
          maxLength: 2000
        status:
          enum:
            - required
            - acknowledged
            - not_applicable
        standard_mappings:
          type: array
          maxItems: 30
          items:
            $ref: '#/components/schemas/standardMapping'
    legalContext:
      type: object
      additionalProperties: false
      required:
        - jurisdiction
        - profile
        - customer_declarations
        - applicability_status
        - disclaimer
      properties:
        jurisdiction:
          type: string
          pattern: ^[A-Z]{2}$
        profile:
          type: string
          minLength: 1
          maxLength: 200
        profiles:
          type: array
          minItems: 1
          maxItems: 20
          uniqueItems: true
          items:
            type: string
            minLength: 1
            maxLength: 200
        customer_declarations:
          type: object
          additionalProperties: false
          properties:
            consumer_facing:
              type: boolean
            service_category:
              type: string
              maxLength: 100
            microenterprise_service_provider:
              type: boolean
            publication_date:
              $ref: '#/components/schemas/date'
            public_sector:
              type: boolean
            archive_content:
              type: boolean
            third_party_control:
              type: boolean
            administrative_service:
              type: boolean
            entity_population:
              type: integer
              minimum: 0
            special_district:
              type: boolean
            claimed_exemption:
              type: string
              maxLength: 500
        applicability_status:
          enum:
            - not_assessed
            - customer_declared_likely
            - customer_declared_unlikely
            - legal_review_recommended
        disclaimer:
          const: This is a customer-declared scope aid, not a legal determination.
    baseline:
      type: object
      additionalProperties: false
      required:
        - scan_id
        - new
        - recurring
        - resolved
      properties:
        scan_id:
          $ref: '#/components/schemas/scanId'
        new:
          $ref: '#/components/schemas/nonNegativeInteger'
        recurring:
          $ref: '#/components/schemas/nonNegativeInteger'
        resolved:
          $ref: '#/components/schemas/nonNegativeInteger'
    canonical-report-v1.schema:
      $schema: https://json-schema.org/draft/2020-12/schema
      $id: urn:accesspreflight:schema:canonical-report:1
      title: AccessPreflight canonical report v1
      description: The immutable machine-readable result artifact. A clean automated result is never represented as legal compliance.
      type: object
      additionalProperties: false
      required:
        - schema_version
        - id
        - scan_id
        - organization_id
        - project_id
        - environment
        - scan_started_at
        - scan_completed_at
        - generated_at
        - status
        - result
        - risk_score
        - risk_score_version
        - fingerprint_version
        - quality_gate
        - asset
        - summary
        - coverage
        - coverage_items
        - reduced_coverage_reasons
        - profiles
        - engine_versions
        - findings
        - manual_checks
        - disclaimer
      properties:
        schema_version:
          enum:
            - 1.0.0
            - 1.1.0
        id:
          $ref: '#/components/schemas/reportId'
        scan_id:
          $ref: '#/components/schemas/scanId'
        organization_id:
          $ref: '#/components/schemas/organizationId'
        project_id:
          $ref: '#/components/schemas/projectId'
        environment:
          enum:
            - test
            - staging
            - production
        scan_started_at:
          $ref: '#/components/schemas/timestamp'
        scan_completed_at:
          $ref: '#/components/schemas/timestamp'
        generated_at:
          $ref: '#/components/schemas/timestamp'
        status:
          const: completed
        result:
          enum:
            - detected_failures
            - review_required
            - no_detected_failures
            - incomplete
        risk_score:
          type: integer
          minimum: 0
          maximum: 100
        risk_score_version:
          type: string
          pattern: ^aps-risk-[1-9]\d*$
        fingerprint_version:
          type: string
          pattern: ^aps-fingerprint-[1-9]\d*$
        quality_gate:
          $ref: '#/components/schemas/qualityGate'
        asset:
          $ref: '#/components/schemas/asset'
        summary:
          $ref: '#/components/schemas/summary'
        coverage:
          $ref: '#/components/schemas/coverageSummary'
        coverage_items:
          type: array
          maxItems: 5000
          items:
            $ref: '#/components/schemas/coverageItem'
        reduced_coverage_reasons:
          type: array
          maxItems: 5000
          uniqueItems: true
          items:
            type: string
            minLength: 1
            maxLength: 500
        profiles:
          type: array
          minItems: 1
          uniqueItems: true
          items:
            $ref: '#/components/schemas/resolvedProfile'
        requirement_coverage:
          type: array
          maxItems: 20000
          items:
            $ref: '#/components/schemas/requirementCoverage'
        rule_pack:
          $ref: '#/components/schemas/resolvedRulePack'
        engine_versions:
          type: object
          minProperties: 1
          maxProperties: 30
          additionalProperties:
            type: string
            minLength: 1
            maxLength: 100
        findings:
          type: array
          maxItems: 10000
          items:
            $ref: '#/components/schemas/finding'
        manual_checks:
          type: array
          maxItems: 1000
          items:
            $ref: '#/components/schemas/manualCheck'
        legal_context:
          $ref: '#/components/schemas/legalContext'
        baseline:
          $ref: '#/components/schemas/baseline'
        disclaimer:
          const: This automated preflight covers only the rules and content identified in the report. Accessibility and legal conformity require consideration of the full service, user journeys, content, applicable law and human evaluation. No result is a legal certification.
      allOf:
        - if:
            properties:
              schema_version:
                const: 1.1.0
            required:
              - schema_version
          then:
            required:
              - requirement_coverage
        - if:
            properties:
              asset:
                properties:
                  type:
                    const: url
                required:
                  - type
            required:
              - asset
          then:
            properties:
              asset:
                required:
                  - fetch
        - if:
            properties:
              asset:
                properties:
                  type:
                    enum:
                      - pdf
                      - docx
                required:
                  - type
            required:
              - asset
          then:
            properties:
              asset:
                required:
                  - page_count
                  - page_count_basis
        - if:
            properties:
              result:
                const: detected_failures
            required:
              - result
          then:
            properties:
              coverage:
                properties:
                  automated_failures:
                    minimum: 1
              findings:
                contains:
                  properties:
                    outcome:
                      const: failed
                  required:
                    - outcome
                minContains: 1
        - if:
            properties:
              result:
                const: no_detected_failures
            required:
              - result
          then:
            properties:
              coverage:
                properties:
                  automated_failures:
                    const: 0
                  heuristic_findings:
                    const: 0
              findings:
                maxItems: 0
        - if:
            properties:
              result:
                const: review_required
            required:
              - result
          then:
            anyOf:
              - properties:
                  findings:
                    contains:
                      properties:
                        outcome:
                          enum:
                            - review
                            - manual
                      required:
                        - outcome
                    minContains: 1
              - properties:
                  manual_checks:
                    contains:
                      properties:
                        status:
                          const: required
                      required:
                        - status
                    minContains: 1
        - if:
            properties:
              asset:
                required:
                  - screenshot
            required:
              - asset
          then:
            properties:
              asset:
                properties:
                  type:
                    const: url
      $defs:
        reportId:
          type: string
          description: Public report identifier backed by an application-generated UUIDv7.
          pattern: ^rpt_[a-f0-9]{12}7[a-f0-9]{3}[89ab][a-f0-9]{15}$
        scanId:
          type: string
          description: Public scan identifier backed by an application-generated UUIDv7.
          pattern: ^scn_[a-f0-9]{12}7[a-f0-9]{3}[89ab][a-f0-9]{15}$
        organizationId:
          type: string
          description: Public organization identifier backed by an application-generated UUIDv7.
          pattern: ^org_[a-f0-9]{12}7[a-f0-9]{3}[89ab][a-f0-9]{15}$
        projectId:
          type: string
          description: Public project identifier backed by an application-generated UUIDv7.
          pattern: ^prj_[a-f0-9]{12}7[a-f0-9]{3}[89ab][a-f0-9]{15}$
        assetId:
          type: string
          description: Public asset identifier backed by an application-generated UUIDv7.
          pattern: ^ast_[a-f0-9]{12}7[a-f0-9]{3}[89ab][a-f0-9]{15}$
        findingId:
          type: string
          description: Public finding identifier backed by an application-generated UUIDv7.
          pattern: ^fnd_[a-f0-9]{12}7[a-f0-9]{3}[89ab][a-f0-9]{15}$
        rulePackId:
          type: string
          description: Public rule-pack identifier backed by an application-generated UUIDv7.
          pattern: ^rpk_[a-f0-9]{12}7[a-f0-9]{3}[89ab][a-f0-9]{15}$
        actorId:
          type: string
          description: Public user, API-key, or service actor identifier backed by an application-generated UUIDv7.
          pattern: ^(?:usr|key|svc)_[a-f0-9]{12}7[a-f0-9]{3}[89ab][a-f0-9]{15}$
        timestamp:
          type: string
          format: date-time
        date:
          type: string
          format: date
        sha256:
          type: string
          pattern: ^[\da-f]{64}$
        qualityGate:
          type: object
          additionalProperties: false
          required:
            - status
            - reasons
          properties:
            status:
              enum:
                - passed
                - failed
                - indeterminate
                - not_configured
            reasons:
              type: array
              maxItems: 100
              items:
                type: string
                maxLength: 500
            policy:
              type: object
              additionalProperties: false
              properties:
                block_on:
                  type: array
                  uniqueItems: true
                  items:
                    $ref: '#/components/schemas/severity'
                block_on_new_only:
                  type: boolean
                max_major_new:
                  type: integer
                  minimum: 0
                require_manual_checklist_acknowledgement:
                  type: boolean
                fail_on_incomplete:
                  type: boolean
        asset:
          type: object
          additionalProperties: false
          required:
            - id
            - type
            - sha256
            - size_bytes
            - media_type
          properties:
            id:
              $ref: '#/components/schemas/assetId'
            type:
              enum:
                - pdf
                - docx
                - url
                - html
            sha256:
              $ref: '#/components/schemas/sha256'
            size_bytes:
              type: integer
              minimum: 0
              maximum: 104857600
            media_type:
              type: string
              minLength: 1
              maxLength: 200
            page_count:
              type: integer
              minimum: 1
              maximum: 500
            page_count_basis:
              enum:
                - pdf_parser
                - docx_structural_estimate_v1
                - not_applicable
            source_retained_until:
              oneOf:
                - $ref: '#/components/schemas/timestamp'
                - type: 'null'
            source_reference_redacted:
              type: string
              maxLength: 2048
            fetch:
              $ref: '#/components/schemas/fetch'
            screenshot:
              $ref: '#/components/schemas/screenshot'
        screenshot:
          type: object
          description: Private artifact metadata only. No object key, bucket, source URL, or download capability is exposed.
          additionalProperties: false
          required:
            - media_type
            - size_bytes
            - sha256
            - width
            - height
            - available_until
          properties:
            media_type:
              const: image/png
            size_bytes:
              type: integer
              minimum: 24
              maximum: 10485760
            sha256:
              $ref: '#/components/schemas/sha256'
            width:
              type: integer
              minimum: 320
              maximum: 3840
            height:
              type: integer
              minimum: 320
              maximum: 2160
            available_until:
              $ref: '#/components/schemas/timestamp'
        fetch:
          type: object
          additionalProperties: false
          required:
            - fetched_at
            - final_url_redacted
            - status_code
          properties:
            fetched_at:
              $ref: '#/components/schemas/timestamp'
            final_url_redacted:
              type: string
              format: uri
              pattern: ^https?://
              maxLength: 2048
            status_code:
              type: integer
              minimum: 100
              maximum: 599
            redirect_count:
              type: integer
              minimum: 0
              maximum: 10
            request_count:
              type: integer
              minimum: 1
              maximum: 1000
            transferred_bytes:
              type: integer
              minimum: 0
              maximum: 104857600
            response_headers:
              type: object
              description: Allowlisted, redacted metadata only.
              maxProperties: 30
              additionalProperties:
                type: string
                maxLength: 2048
            dom_snapshot_sha256:
              $ref: '#/components/schemas/sha256'
        summary:
          type: object
          additionalProperties: false
          required:
            - blocker
            - critical
            - major
            - minor
            - info
            - manual_checks_required
          properties:
            blocker:
              $ref: '#/components/schemas/nonNegativeInteger'
            critical:
              $ref: '#/components/schemas/nonNegativeInteger'
            major:
              $ref: '#/components/schemas/nonNegativeInteger'
            minor:
              $ref: '#/components/schemas/nonNegativeInteger'
            info:
              $ref: '#/components/schemas/nonNegativeInteger'
            manual_checks_required:
              $ref: '#/components/schemas/nonNegativeInteger'
        coverageSummary:
          type: object
          additionalProperties: false
          required:
            - rules_configured
            - rules_executed
            - rules_not_applicable
            - rules_reduced_coverage
            - manual_checks_required
            - automated_failures
            - heuristic_findings
          properties:
            rules_configured:
              $ref: '#/components/schemas/nonNegativeInteger'
            rules_executed:
              $ref: '#/components/schemas/nonNegativeInteger'
            rules_not_applicable:
              $ref: '#/components/schemas/nonNegativeInteger'
            rules_reduced_coverage:
              $ref: '#/components/schemas/nonNegativeInteger'
            manual_checks_required:
              $ref: '#/components/schemas/nonNegativeInteger'
            automated_failures:
              $ref: '#/components/schemas/nonNegativeInteger'
            heuristic_findings:
              $ref: '#/components/schemas/nonNegativeInteger'
        coverageItem:
          type: object
          additionalProperties: false
          required:
            - rule_id
            - automation
            - outcome
          properties:
            rule_id:
              type: string
              minLength: 1
              maxLength: 200
            automation:
              enum:
                - deterministic
                - partial
                - heuristic
                - manual
            outcome:
              enum:
                - passed
                - failed
                - not_applicable
                - reduced_coverage
                - not_executed
                - manual
            reason:
              type: string
              minLength: 1
              maxLength: 500
        nonNegativeInteger:
          type: integer
          minimum: 0
        resolvedProfile:
          type: object
          additionalProperties: false
          required:
            - id
            - version
            - label
            - mapping_version
            - mapping_status
          properties:
            id:
              type: string
              pattern: ^[a-z0-9]+(?:[._-][a-z0-9]+)*$
              maxLength: 150
            version:
              type: string
              pattern: ^\d+(?:\.\d+){1,2}(?:-[0-9A-Za-z]+(?:[.-][0-9A-Za-z]+)*)?$
              maxLength: 40
            label:
              type: string
              minLength: 1
              maxLength: 200
            mapping_version:
              type: string
              minLength: 1
              maxLength: 100
            mapping_status:
              enum:
                - pending_review
                - approved
                - beta_reviewed
                - preview
            profile_kind:
              type: string
              minLength: 1
              maxLength: 100
            legal_effect:
              type: string
              minLength: 1
              maxLength: 100
            harmonization_status:
              type: string
              minLength: 1
              maxLength: 160
            scope_summary:
              type: string
              minLength: 1
              maxLength: 2000
            coverage_summary:
              type: string
              minLength: 1
              maxLength: 2000
            unsupported_scope:
              type: array
              maxItems: 30
              uniqueItems: true
              items:
                type: string
                minLength: 1
                maxLength: 500
            required_declarations:
              type: array
              maxItems: 20
              uniqueItems: true
              items:
                type: string
                enum:
                  - administrative_service
                  - archive_content
                  - claimed_exemption
                  - consumer_facing
                  - entity_population
                  - microenterprise_service_provider
                  - public_sector
                  - publication_date
                  - service_category
                  - special_district
                  - third_party_control
            specialist_review_status:
              enum:
                - required
                - approved
                - preview
            allowed_claim:
              type: string
              minLength: 1
              maxLength: 1000
            prohibited_claims:
              type: array
              maxItems: 30
              uniqueItems: true
              items:
                type: string
                minLength: 1
                maxLength: 300
            engine_requirement:
              $ref: '#/components/schemas/profileEngineRequirement'
            disclaimer:
              type: string
              minLength: 40
              maxLength: 3000
            source_urls:
              type: array
              maxItems: 20
              items:
                type: string
                format: uri
                maxLength: 2048
            manifest_sha256:
              $ref: '#/components/schemas/sha256'
            rules_manifest_sha256:
              $ref: '#/components/schemas/sha256'
            mapping_manifest_sha256:
              $ref: '#/components/schemas/sha256'
            mapping_denominator:
              $ref: '#/components/schemas/mappingDenominator'
            mapping_approval_status:
              enum:
                - not_obtained
                - approved
        mappingDenominator:
          type: object
          additionalProperties: false
          required:
            - total
            - deterministic
            - partial
            - heuristic
            - manual
            - unsupported
            - by_asset_type
          properties:
            total:
              $ref: '#/components/schemas/nonNegativeInteger'
            deterministic:
              $ref: '#/components/schemas/nonNegativeInteger'
            partial:
              $ref: '#/components/schemas/nonNegativeInteger'
            heuristic:
              $ref: '#/components/schemas/nonNegativeInteger'
            manual:
              $ref: '#/components/schemas/nonNegativeInteger'
            unsupported:
              $ref: '#/components/schemas/nonNegativeInteger'
            by_asset_type:
              type: object
              additionalProperties:
                type: integer
                minimum: 0
        requirementCoverage:
          type: object
          additionalProperties: false
          required:
            - profile_id
            - profile_version
            - mapping_version
            - mapping_manifest_sha256
            - mapping_approval_status
            - source
            - requirement_id
            - short_label
            - asset_type
            - classification
            - implemented_rule_ids
            - outcome
            - rationale
            - limitations
          properties:
            profile_id:
              type: string
              minLength: 1
              maxLength: 150
            profile_version:
              type: string
              minLength: 1
              maxLength: 40
            mapping_version:
              type: string
              minLength: 1
              maxLength: 100
            mapping_manifest_sha256:
              $ref: '#/components/schemas/sha256'
            mapping_approval_status:
              enum:
                - not_obtained
                - approved
            source:
              $ref: '#/components/schemas/normativeSource'
            requirement_id:
              type: string
              minLength: 1
              maxLength: 200
            short_label:
              type: string
              minLength: 1
              maxLength: 300
            asset_type:
              enum:
                - url
                - html
                - pdf
                - docx
            classification:
              enum:
                - deterministic
                - partial
                - heuristic
                - manual
                - unsupported
            implemented_rule_ids:
              type: array
              uniqueItems: true
              items:
                type: string
                minLength: 1
                maxLength: 200
            applicability_condition:
              $ref: '#/components/schemas/applicabilityCondition'
            outcome:
              enum:
                - failed
                - passed
                - no_detected_failure
                - review_required
                - manual
                - unsupported
                - not_assessed
                - not_applicable
                - reduced_coverage
                - not_executed
            rationale:
              type: string
              minLength: 1
              maxLength: 1000
            limitations:
              type: string
              minLength: 1
              maxLength: 1000
        normativeSource:
          type: object
          additionalProperties: false
          required:
            - id
            - title
            - edition
            - kind
            - official_url
            - verified_at
          properties:
            id:
              type: string
              minLength: 1
              maxLength: 150
            title:
              type: string
              minLength: 1
              maxLength: 300
            edition:
              type: string
              minLength: 1
              maxLength: 100
            kind:
              enum:
                - normative
                - machine_profile
                - legal
                - supplemental
            official_url:
              type: string
              format: uri
              pattern: ^https://
            verified_at:
              $ref: '#/components/schemas/date'
        applicabilityCondition:
          type: object
          additionalProperties: false
          required:
            - declaration
            - operator
            - value
          properties:
            declaration:
              type: string
              minLength: 1
              maxLength: 100
            operator:
              enum:
                - equals
                - not_equals
                - present
            value: {}
        profileEngineRequirement:
          type: object
          additionalProperties: false
          required:
            - name
            - flavour
            - minimum_version
          properties:
            name:
              type: string
              minLength: 1
              maxLength: 100
            flavour:
              type: string
              minLength: 1
              maxLength: 100
            minimum_version:
              type: string
              minLength: 1
              maxLength: 100
            exact_version:
              type: string
              minLength: 1
              maxLength: 100
        resolvedRulePack:
          type: object
          additionalProperties: false
          required:
            - id
            - version
          properties:
            id:
              $ref: '#/components/schemas/rulePackId'
            version:
              type: integer
              minimum: 1
            sha256:
              $ref: '#/components/schemas/sha256'
        finding:
          type: object
          additionalProperties: false
          required:
            - id
            - asset_id
            - asset_type
            - fingerprint
            - rule
            - outcome
            - severity
            - confidence
            - automation
            - title
            - description
            - scope
            - evidence
            - standards
            - occurrence_count
            - sample_occurrences
            - remediation
            - manual_follow_up
            - baseline_state
            - suppressed
            - first_seen_at
            - last_seen_at
          properties:
            id:
              $ref: '#/components/schemas/findingId'
            asset_id:
              $ref: '#/components/schemas/assetId'
            asset_type:
              enum:
                - pdf
                - docx
                - url
                - html
            fingerprint:
              type: string
              minLength: 16
              maxLength: 256
            fingerprint_version:
              type: string
              pattern: ^aps-fingerprint-[1-9][0-9]*$
            rule:
              $ref: '#/components/schemas/ruleReference'
            outcome:
              enum:
                - failed
                - review
                - manual
            severity:
              $ref: '#/components/schemas/severity'
            confidence:
              enum:
                - certain
                - high
                - medium
                - low
                - human_required
            automation:
              enum:
                - deterministic
                - partial
                - heuristic
                - manual
            title:
              type: string
              minLength: 1
              maxLength: 200
            description:
              type: string
              minLength: 1
              maxLength: 2000
            document_wide_reason:
              type: string
              minLength: 1
              maxLength: 512
              description: Why the finding applies to the document as a whole when no narrower locator is appropriate.
            scope:
              enum:
                - document
                - page
                - element
                - occurrence
            evidence:
              $ref: '#/components/schemas/evidence'
            standards:
              type: array
              minItems: 1
              maxItems: 30
              items:
                $ref: '#/components/schemas/standardMapping'
            occurrence_count:
              type: integer
              minimum: 1
              maximum: 1000000
            sample_occurrences:
              type: array
              minItems: 1
              maxItems: 20
              items:
                $ref: '#/components/schemas/evidence'
            remediation:
              $ref: '#/components/schemas/remediation'
            manual_follow_up:
              type: array
              maxItems: 20
              items:
                type: string
                maxLength: 1000
            baseline_state:
              enum:
                - new
                - recurring
                - not_compared
            suppressed:
              type: boolean
            suppression:
              $ref: '#/components/schemas/suppression'
            first_seen_at:
              $ref: '#/components/schemas/timestamp'
            last_seen_at:
              $ref: '#/components/schemas/timestamp'
          allOf:
            - if:
                properties:
                  suppressed:
                    const: true
                required:
                  - suppressed
              then:
                required:
                  - suppression
              else:
                not:
                  required:
                    - suppression
        severity:
          enum:
            - blocker
            - critical
            - major
            - minor
            - info
        ruleReference:
          type: object
          additionalProperties: false
          required:
            - id
            - version
            - native_engine
            - native_rule_id
          properties:
            id:
              type: string
              pattern: ^[a-z0-9]+(?:[._-][a-z0-9]+)*$
              maxLength: 200
            version:
              type: string
              pattern: ^\d+\.\d+\.\d+$
            native_engine:
              type: string
              minLength: 1
              maxLength: 100
            native_rule_id:
              type: string
              minLength: 1
              maxLength: 200
        evidence:
          type: object
          additionalProperties: false
          required:
            - locator_type
            - locator
          properties:
            locator_type:
              enum:
                - document
                - pdf_catalog
                - pdf_page
                - pdf_object
                - pdf_mcid
                - pdf_annotation
                - dom_selector
                - accessibility_path
                - docx_part
                - docx_paragraph
                - docx_relationship
                - docx_object
            locator:
              type: string
              minLength: 1
              maxLength: 1000
            page:
              type: integer
              minimum: 1
              maximum: 500
            observed:
              type: string
              maxLength: 512
            context:
              type: string
              maxLength: 512
        standardMapping:
          type: object
          additionalProperties: false
          required:
            - profile
            - version
            - reference
            - mapping_version
          properties:
            profile:
              type: string
              minLength: 1
              maxLength: 150
            version:
              type: string
              pattern: ^\d+(?:\.\d+){1,2}(?:-[0-9A-Za-z]+(?:[.-][0-9A-Za-z]+)*)?$
              maxLength: 40
            reference:
              type: string
              minLength: 1
              maxLength: 300
            mapping_version:
              type: string
              minLength: 1
              maxLength: 100
            mapping_sha256:
              $ref: '#/components/schemas/sha256'
            source_id:
              type: string
              minLength: 1
              maxLength: 150
            requirement_id:
              type: string
              minLength: 1
              maxLength: 200
            asset_type:
              enum:
                - url
                - html
                - pdf
                - docx
            classification:
              enum:
                - deterministic
                - partial
                - heuristic
                - manual
                - unsupported
            short_label:
              type: string
              minLength: 1
              maxLength: 300
            limitations:
              type: string
              minLength: 1
              maxLength: 1000
        remediation:
          type: object
          additionalProperties: false
          required:
            - summary
            - steps
          properties:
            summary:
              type: string
              minLength: 1
              maxLength: 1000
            steps:
              type: array
              minItems: 1
              maxItems: 20
              items:
                type: string
                minLength: 1
                maxLength: 1000
        manualCheck:
          type: object
          additionalProperties: false
          required:
            - id
            - title
            - guidance
            - status
          properties:
            id:
              type: string
              pattern: ^[a-z0-9]+(?:[._-][a-z0-9]+)*$
              maxLength: 200
            title:
              type: string
              minLength: 1
              maxLength: 200
            guidance:
              type: string
              minLength: 1
              maxLength: 2000
            status:
              enum:
                - required
                - acknowledged
                - not_applicable
            standard_mappings:
              type: array
              maxItems: 30
              items:
                $ref: '#/components/schemas/standardMapping'
        suppression:
          type: object
          additionalProperties: false
          required:
            - actor_id
            - reason
            - created_at
          properties:
            actor_id:
              $ref: '#/components/schemas/actorId'
            reason:
              type: string
              minLength: 1
              maxLength: 1000
            created_at:
              $ref: '#/components/schemas/timestamp'
            expires_at:
              oneOf:
                - $ref: '#/components/schemas/timestamp'
                - type: 'null'
        legalContext:
          type: object
          additionalProperties: false
          required:
            - jurisdiction
            - profile
            - customer_declarations
            - applicability_status
            - disclaimer
          properties:
            jurisdiction:
              type: string
              pattern: ^[A-Z]{2}$
            profile:
              type: string
              minLength: 1
              maxLength: 200
            profiles:
              type: array
              minItems: 1
              maxItems: 20
              uniqueItems: true
              items:
                type: string
                minLength: 1
                maxLength: 200
            customer_declarations:
              type: object
              additionalProperties: false
              properties:
                consumer_facing:
                  type: boolean
                service_category:
                  type: string
                  maxLength: 100
                microenterprise_service_provider:
                  type: boolean
                publication_date:
                  $ref: '#/components/schemas/date'
                public_sector:
                  type: boolean
                archive_content:
                  type: boolean
                third_party_control:
                  type: boolean
                administrative_service:
                  type: boolean
                entity_population:
                  type: integer
                  minimum: 0
                special_district:
                  type: boolean
                claimed_exemption:
                  type: string
                  maxLength: 500
            applicability_status:
              enum:
                - not_assessed
                - customer_declared_likely
                - customer_declared_unlikely
                - legal_review_recommended
            disclaimer:
              const: This is a customer-declared scope aid, not a legal determination.
        baseline:
          type: object
          additionalProperties: false
          required:
            - scan_id
            - new
            - recurring
            - resolved
          properties:
            scan_id:
              $ref: '#/components/schemas/scanId'
            new:
              $ref: '#/components/schemas/nonNegativeInteger'
            recurring:
              $ref: '#/components/schemas/nonNegativeInteger'
            resolved:
              $ref: '#/components/schemas/nonNegativeInteger'
    manifestId:
      type: string
      description: Public evidence-manifest identifier backed by an application-generated UUIDv7.
      pattern: ^evm_[a-f0-9]{12}7[a-f0-9]{3}[89ab][a-f0-9]{15}$
    asset-2:
      type: object
      additionalProperties: false
      required:
        - id
        - type
        - sha256
        - size_bytes
        - source_retention
      properties:
        id:
          $ref: '#/components/schemas/assetId'
        type:
          enum:
            - pdf
            - docx
            - url
            - html
        sha256:
          $ref: '#/components/schemas/sha256'
        size_bytes:
          type: integer
          minimum: 0
          maximum: 104857600
        page_count:
          type: integer
          minimum: 1
          maximum: 500
        page_count_basis:
          enum:
            - pdf_parser
            - docx_structural_estimate_v1
            - not_applicable
        source_reference_redacted:
          type: string
          maxLength: 2048
        source_retention:
          type: object
          additionalProperties: false
          required:
            - source_included
            - delete_after
          properties:
            source_included:
              type: boolean
              description: True only when explicit source-retention configuration caused the source artifact to be included in the evidence archive.
            delete_after:
              oneOf:
                - $ref: '#/components/schemas/timestamp'
                - type: 'null'
            deleted_at:
              oneOf:
                - $ref: '#/components/schemas/timestamp'
                - type: 'null'
            source_artifact:
              type: object
              additionalProperties: false
              required:
                - media_type
                - size_bytes
                - sha256
              properties:
                file_name_redacted:
                  type: string
                  minLength: 1
                  maxLength: 255
                media_type:
                  type: string
                  minLength: 1
                  maxLength: 255
                size_bytes:
                  type: integer
                  minimum: 1
                  maximum: 104857600
                sha256:
                  $ref: '#/components/schemas/sha256'
          allOf:
            - if:
                properties:
                  source_included:
                    const: true
                required:
                  - source_included
              then:
                required:
                  - source_artifact
              else:
                not:
                  required:
                    - source_artifact
        fetch:
          type: object
          additionalProperties: false
          required:
            - fetched_at
            - final_url_redacted
          properties:
            fetched_at:
              $ref: '#/components/schemas/timestamp'
            final_url_redacted:
              type: string
              format: uri
              pattern: ^https?://
              maxLength: 2048
            status_code:
              type: integer
              minimum: 100
              maximum: 599
            redirect_count:
              type: integer
              minimum: 0
              maximum: 10
            request_count:
              type: integer
              minimum: 1
              maximum: 1000
            transferred_bytes:
              type: integer
              minimum: 0
              maximum: 104857600
            response_metadata_sha256:
              $ref: '#/components/schemas/sha256'
            dom_snapshot_sha256:
              $ref: '#/components/schemas/sha256'
      allOf:
        - if:
            properties:
              type:
                const: url
            required:
              - type
          then:
            required:
              - fetch
        - if:
            properties:
              type:
                enum:
                  - pdf
                  - docx
            required:
              - type
          then:
            required:
              - page_count
              - page_count_basis
    scan:
      type: object
      additionalProperties: false
      required:
        - created_at
        - started_at
        - completed_at
        - result
        - options_sha256
        - normalizer_version
        - fingerprint_version
        - risk_score_version
      properties:
        created_at:
          $ref: '#/components/schemas/timestamp'
        started_at:
          $ref: '#/components/schemas/timestamp'
        completed_at:
          $ref: '#/components/schemas/timestamp'
        result:
          enum:
            - detected_failures
            - review_required
            - no_detected_failures
            - incomplete
        options_sha256:
          $ref: '#/components/schemas/sha256'
        normalizer_version:
          type: string
          pattern: ^aps-normalizer-[1-9]\d*$
        fingerprint_version:
          type: string
          pattern: ^aps-fingerprint-[1-9]\d*$
        risk_score_version:
          type: string
          pattern: ^aps-risk-[1-9]\d*$
    mappingDenominator-2:
      type: object
      additionalProperties: false
      required:
        - total
        - deterministic
        - partial
        - heuristic
        - manual
        - unsupported
        - by_asset_type
      properties:
        total:
          type: integer
          minimum: 0
        deterministic:
          type: integer
          minimum: 0
        partial:
          type: integer
          minimum: 0
        heuristic:
          type: integer
          minimum: 0
        manual:
          type: integer
          minimum: 0
        unsupported:
          type: integer
          minimum: 0
        by_asset_type:
          type: object
          additionalProperties:
            type: integer
            minimum: 0
    profile:
      type: object
      additionalProperties: false
      required:
        - id
        - version
        - mapping_version
        - mapping_status
        - manifest_sha256
      properties:
        id:
          type: string
          minLength: 1
          maxLength: 150
        version:
          type: string
          pattern: ^\d+(?:\.\d+){1,2}(?:-[0-9A-Za-z]+(?:[.-][0-9A-Za-z]+)*)?$
          maxLength: 40
        mapping_version:
          type: string
          minLength: 1
          maxLength: 100
        mapping_status:
          enum:
            - pending_review
            - approved
            - beta_reviewed
            - preview
        profile_kind:
          type: string
          minLength: 1
          maxLength: 100
        legal_effect:
          type: string
          minLength: 1
          maxLength: 100
        harmonization_status:
          type: string
          minLength: 1
          maxLength: 160
        scope_summary:
          type: string
          minLength: 1
          maxLength: 2000
        coverage_summary:
          type: string
          minLength: 1
          maxLength: 2000
        unsupported_scope:
          type: array
          maxItems: 30
          uniqueItems: true
          items:
            type: string
            minLength: 1
            maxLength: 500
        required_declarations:
          type: array
          maxItems: 20
          uniqueItems: true
          items:
            type: string
            enum:
              - administrative_service
              - archive_content
              - claimed_exemption
              - consumer_facing
              - entity_population
              - microenterprise_service_provider
              - public_sector
              - publication_date
              - service_category
              - special_district
              - third_party_control
        specialist_review_status:
          enum:
            - required
            - approved
            - preview
        allowed_claim:
          type: string
          minLength: 1
          maxLength: 1000
        prohibited_claims:
          type: array
          maxItems: 30
          uniqueItems: true
          items:
            type: string
            minLength: 1
            maxLength: 300
        engine_requirement:
          type: object
          additionalProperties: false
          required:
            - name
            - flavour
            - minimum_version
          properties:
            name:
              type: string
              minLength: 1
              maxLength: 100
            flavour:
              type: string
              minLength: 1
              maxLength: 100
            minimum_version:
              type: string
              minLength: 1
              maxLength: 100
            exact_version:
              type: string
              minLength: 1
              maxLength: 100
        manifest_sha256:
          $ref: '#/components/schemas/sha256'
        rules_manifest_sha256:
          $ref: '#/components/schemas/sha256'
        mapping_manifest_sha256:
          $ref: '#/components/schemas/sha256'
        mapping_denominator:
          $ref: '#/components/schemas/mappingDenominator-2'
        mapping_approval_status:
          enum:
            - not_obtained
            - approved
        disclaimer:
          type: string
          minLength: 40
          maxLength: 3000
        source_urls:
          type: array
          maxItems: 20
          items:
            type: string
            format: uri
            maxLength: 2048
    rulePack:
      type: object
      additionalProperties: false
      required:
        - id
        - version
        - sha256
      properties:
        id:
          $ref: '#/components/schemas/rulePackId'
        version:
          type: integer
          minimum: 1
        sha256:
          $ref: '#/components/schemas/sha256'
    coverage:
      type: object
      additionalProperties: false
      required:
        - rules_configured
        - rules_executed
        - rules_not_applicable
        - rules_reduced_coverage
        - manual_checks_required
        - automated_failures
        - heuristic_findings
      properties:
        rules_configured:
          $ref: '#/components/schemas/nonNegativeInteger'
        rules_executed:
          $ref: '#/components/schemas/nonNegativeInteger'
        rules_not_applicable:
          $ref: '#/components/schemas/nonNegativeInteger'
        rules_reduced_coverage:
          $ref: '#/components/schemas/nonNegativeInteger'
        manual_checks_required:
          $ref: '#/components/schemas/nonNegativeInteger'
        automated_failures:
          $ref: '#/components/schemas/nonNegativeInteger'
        heuristic_findings:
          $ref: '#/components/schemas/nonNegativeInteger'
    findingSet:
      type: object
      description: Digest of the canonical report findings array; the report artifact contains the complete normalized findings.
      additionalProperties: false
      required:
        - count
        - canonical_json_sha256
      properties:
        count:
          $ref: '#/components/schemas/nonNegativeInteger'
        canonical_json_sha256:
          $ref: '#/components/schemas/sha256'
    suppression-2:
      type: object
      additionalProperties: false
      required:
        - finding_fingerprint
        - actor_id
        - reason
        - created_at
      properties:
        finding_fingerprint:
          type: string
          minLength: 16
          maxLength: 256
        actor_id:
          $ref: '#/components/schemas/actorId'
        reason:
          type: string
          minLength: 1
          maxLength: 1000
        created_at:
          $ref: '#/components/schemas/timestamp'
        expires_at:
          oneOf:
            - $ref: '#/components/schemas/timestamp'
            - type: 'null'
    report:
      type: object
      additionalProperties: false
      required:
        - format
        - schema_version
        - media_type
        - size_bytes
        - sha256
      properties:
        format:
          enum:
            - json
            - html
        schema_version:
          enum:
            - 1.0.0
            - 1.1.0
        media_type:
          enum:
            - application/vnd.accesspreflight.report+json
            - text/html
        size_bytes:
          type: integer
          minimum: 1
          maximum: 104857600
        sha256:
          $ref: '#/components/schemas/sha256'
      allOf:
        - if:
            properties:
              format:
                const: json
            required:
              - format
          then:
            properties:
              media_type:
                const: application/vnd.accesspreflight.report+json
        - if:
            properties:
              format:
                const: html
            required:
              - format
          then:
            properties:
              media_type:
                const: text/html
    payload:
      type: object
      additionalProperties: false
      required:
        - manifest_id
        - organization_id
        - project_id
        - environment
        - scan_id
        - asset
        - scan
        - engines
        - profiles
        - coverage
        - findings
        - reports
        - created_at
        - disclaimer
      properties:
        manifest_id:
          $ref: '#/components/schemas/manifestId'
        organization_id:
          $ref: '#/components/schemas/organizationId'
        project_id:
          $ref: '#/components/schemas/projectId'
        environment:
          enum:
            - test
            - staging
            - production
        external_reference:
          type: string
          maxLength: 500
        scan_id:
          $ref: '#/components/schemas/scanId'
        asset:
          $ref: '#/components/schemas/asset-2'
        scan:
          $ref: '#/components/schemas/scan'
        engines:
          type: object
          minProperties: 1
          maxProperties: 30
          additionalProperties:
            type: string
            minLength: 1
            maxLength: 100
        profiles:
          type: array
          minItems: 1
          maxItems: 30
          items:
            $ref: '#/components/schemas/profile'
        rule_pack:
          $ref: '#/components/schemas/rulePack'
        coverage:
          $ref: '#/components/schemas/coverage'
        findings:
          $ref: '#/components/schemas/findingSet'
        suppressions:
          type: array
          maxItems: 10000
          items:
            $ref: '#/components/schemas/suppression-2'
        reports:
          type: array
          minItems: 2
          maxItems: 10
          uniqueItems: true
          items:
            $ref: '#/components/schemas/report'
          allOf:
            - contains:
                properties:
                  format:
                    const: json
                required:
                  - format
              minContains: 1
              maxContains: 1
            - contains:
                properties:
                  format:
                    const: html
                required:
                  - format
              minContains: 1
              maxContains: 1
        created_at:
          $ref: '#/components/schemas/timestamp'
        disclaimer:
          const: This automated preflight covers only the rules and content identified in the report. Accessibility and legal conformity require consideration of the full service, user journeys, content, applicable law and human evaluation. No result is a legal certification.
    signature:
      type: object
      additionalProperties: false
      required:
        - algorithm
        - canonicalization
        - key_id
        - value
      properties:
        algorithm:
          const: Ed25519-SHA256
        canonicalization:
          const: RFC8785
        key_id:
          type: string
          pattern: ^[A-Za-z0-9._-]{1,100}$
        value:
          description: Base64url-encoded Ed25519 signature over the canonical payload's SHA-256 digest, without padding.
          type: string
          pattern: ^[A-Za-z0-9_-]{86}$
    evidence-manifest-v1.schema:
      $schema: https://json-schema.org/draft/2020-12/schema
      $id: urn:accesspreflight:schema:evidence-manifest:1
      title: AccessPreflight evidence manifest v1
      description: Signed evidence metadata. The Ed25519 signature covers the SHA-256 digest of the RFC 8785 canonical JSON representation of the payload member only.
      type: object
      additionalProperties: false
      required:
        - schema_version
        - payload
        - signature
      properties:
        schema_version:
          const: 1.0.0
        payload:
          $ref: '#/components/schemas/payload'
        signature:
          $ref: '#/components/schemas/signature'
      $defs:
        manifestId:
          type: string
          description: Public evidence-manifest identifier backed by an application-generated UUIDv7.
          pattern: ^evm_[a-f0-9]{12}7[a-f0-9]{3}[89ab][a-f0-9]{15}$
        organizationId:
          type: string
          description: Public organization identifier backed by an application-generated UUIDv7.
          pattern: ^org_[a-f0-9]{12}7[a-f0-9]{3}[89ab][a-f0-9]{15}$
        projectId:
          type: string
          description: Public project identifier backed by an application-generated UUIDv7.
          pattern: ^prj_[a-f0-9]{12}7[a-f0-9]{3}[89ab][a-f0-9]{15}$
        scanId:
          type: string
          description: Public scan identifier backed by an application-generated UUIDv7.
          pattern: ^scn_[a-f0-9]{12}7[a-f0-9]{3}[89ab][a-f0-9]{15}$
        assetId:
          type: string
          description: Public asset identifier backed by an application-generated UUIDv7.
          pattern: ^ast_[a-f0-9]{12}7[a-f0-9]{3}[89ab][a-f0-9]{15}$
        rulePackId:
          type: string
          description: Public rule-pack identifier backed by an application-generated UUIDv7.
          pattern: ^rpk_[a-f0-9]{12}7[a-f0-9]{3}[89ab][a-f0-9]{15}$
        actorId:
          type: string
          description: Public user, API-key, or service actor identifier backed by an application-generated UUIDv7.
          pattern: ^(?:usr|key|svc)_[a-f0-9]{12}7[a-f0-9]{3}[89ab][a-f0-9]{15}$
        timestamp:
          type: string
          format: date-time
        sha256:
          type: string
          pattern: ^[\da-f]{64}$
        payload:
          type: object
          additionalProperties: false
          required:
            - manifest_id
            - organization_id
            - project_id
            - environment
            - scan_id
            - asset
            - scan
            - engines
            - profiles
            - coverage
            - findings
            - reports
            - created_at
            - disclaimer
          properties:
            manifest_id:
              $ref: '#/components/schemas/manifestId'
            organization_id:
              $ref: '#/components/schemas/organizationId'
            project_id:
              $ref: '#/components/schemas/projectId'
            environment:
              enum:
                - test
                - staging
                - production
            external_reference:
              type: string
              maxLength: 500
            scan_id:
              $ref: '#/components/schemas/scanId'
            asset:
              $ref: '#/components/schemas/asset-2'
            scan:
              $ref: '#/components/schemas/scan'
            engines:
              type: object
              minProperties: 1
              maxProperties: 30
              additionalProperties:
                type: string
                minLength: 1
                maxLength: 100
            profiles:
              type: array
              minItems: 1
              maxItems: 30
              items:
                $ref: '#/components/schemas/profile'
            rule_pack:
              $ref: '#/components/schemas/rulePack'
            coverage:
              $ref: '#/components/schemas/coverage'
            findings:
              $ref: '#/components/schemas/findingSet'
            suppressions:
              type: array
              maxItems: 10000
              items:
                $ref: '#/components/schemas/suppression-2'
            reports:
              type: array
              minItems: 2
              maxItems: 10
              uniqueItems: true
              items:
                $ref: '#/components/schemas/report'
              allOf:
                - contains:
                    properties:
                      format:
                        const: json
                    required:
                      - format
                  minContains: 1
                  maxContains: 1
                - contains:
                    properties:
                      format:
                        const: html
                    required:
                      - format
                  minContains: 1
                  maxContains: 1
            created_at:
              $ref: '#/components/schemas/timestamp'
            disclaimer:
              const: This automated preflight covers only the rules and content identified in the report. Accessibility and legal conformity require consideration of the full service, user journeys, content, applicable law and human evaluation. No result is a legal certification.
        asset:
          type: object
          additionalProperties: false
          required:
            - id
            - type
            - sha256
            - size_bytes
            - source_retention
          properties:
            id:
              $ref: '#/components/schemas/assetId'
            type:
              enum:
                - pdf
                - docx
                - url
                - html
            sha256:
              $ref: '#/components/schemas/sha256'
            size_bytes:
              type: integer
              minimum: 0
              maximum: 104857600
            page_count:
              type: integer
              minimum: 1
              maximum: 500
            page_count_basis:
              enum:
                - pdf_parser
                - docx_structural_estimate_v1
                - not_applicable
            source_reference_redacted:
              type: string
              maxLength: 2048
            source_retention:
              type: object
              additionalProperties: false
              required:
                - source_included
                - delete_after
              properties:
                source_included:
                  type: boolean
                  description: True only when explicit source-retention configuration caused the source artifact to be included in the evidence archive.
                delete_after:
                  oneOf:
                    - $ref: '#/components/schemas/timestamp'
                    - type: 'null'
                deleted_at:
                  oneOf:
                    - $ref: '#/components/schemas/timestamp'
                    - type: 'null'
                source_artifact:
                  type: object
                  additionalProperties: false
                  required:
                    - media_type
                    - size_bytes
                    - sha256
                  properties:
                    file_name_redacted:
                      type: string
                      minLength: 1
                      maxLength: 255
                    media_type:
                      type: string
                      minLength: 1
                      maxLength: 255
                    size_bytes:
                      type: integer
                      minimum: 1
                      maximum: 104857600
                    sha256:
                      $ref: '#/components/schemas/sha256'
              allOf:
                - if:
                    properties:
                      source_included:
                        const: true
                    required:
                      - source_included
                  then:
                    required:
                      - source_artifact
                  else:
                    not:
                      required:
                        - source_artifact
            fetch:
              type: object
              additionalProperties: false
              required:
                - fetched_at
                - final_url_redacted
              properties:
                fetched_at:
                  $ref: '#/components/schemas/timestamp'
                final_url_redacted:
                  type: string
                  format: uri
                  pattern: ^https?://
                  maxLength: 2048
                status_code:
                  type: integer
                  minimum: 100
                  maximum: 599
                redirect_count:
                  type: integer
                  minimum: 0
                  maximum: 10
                request_count:
                  type: integer
                  minimum: 1
                  maximum: 1000
                transferred_bytes:
                  type: integer
                  minimum: 0
                  maximum: 104857600
                response_metadata_sha256:
                  $ref: '#/components/schemas/sha256'
                dom_snapshot_sha256:
                  $ref: '#/components/schemas/sha256'
          allOf:
            - if:
                properties:
                  type:
                    const: url
                required:
                  - type
              then:
                required:
                  - fetch
            - if:
                properties:
                  type:
                    enum:
                      - pdf
                      - docx
                required:
                  - type
              then:
                required:
                  - page_count
                  - page_count_basis
        scan:
          type: object
          additionalProperties: false
          required:
            - created_at
            - started_at
            - completed_at
            - result
            - options_sha256
            - normalizer_version
            - fingerprint_version
            - risk_score_version
          properties:
            created_at:
              $ref: '#/components/schemas/timestamp'
            started_at:
              $ref: '#/components/schemas/timestamp'
            completed_at:
              $ref: '#/components/schemas/timestamp'
            result:
              enum:
                - detected_failures
                - review_required
                - no_detected_failures
                - incomplete
            options_sha256:
              $ref: '#/components/schemas/sha256'
            normalizer_version:
              type: string
              pattern: ^aps-normalizer-[1-9]\d*$
            fingerprint_version:
              type: string
              pattern: ^aps-fingerprint-[1-9]\d*$
            risk_score_version:
              type: string
              pattern: ^aps-risk-[1-9]\d*$
        profile:
          type: object
          additionalProperties: false
          required:
            - id
            - version
            - mapping_version
            - mapping_status
            - manifest_sha256
          properties:
            id:
              type: string
              minLength: 1
              maxLength: 150
            version:
              type: string
              pattern: ^\d+(?:\.\d+){1,2}(?:-[0-9A-Za-z]+(?:[.-][0-9A-Za-z]+)*)?$
              maxLength: 40
            mapping_version:
              type: string
              minLength: 1
              maxLength: 100
            mapping_status:
              enum:
                - pending_review
                - approved
                - beta_reviewed
                - preview
            profile_kind:
              type: string
              minLength: 1
              maxLength: 100
            legal_effect:
              type: string
              minLength: 1
              maxLength: 100
            harmonization_status:
              type: string
              minLength: 1
              maxLength: 160
            scope_summary:
              type: string
              minLength: 1
              maxLength: 2000
            coverage_summary:
              type: string
              minLength: 1
              maxLength: 2000
            unsupported_scope:
              type: array
              maxItems: 30
              uniqueItems: true
              items:
                type: string
                minLength: 1
                maxLength: 500
            required_declarations:
              type: array
              maxItems: 20
              uniqueItems: true
              items:
                type: string
                enum:
                  - administrative_service
                  - archive_content
                  - claimed_exemption
                  - consumer_facing
                  - entity_population
                  - microenterprise_service_provider
                  - public_sector
                  - publication_date
                  - service_category
                  - special_district
                  - third_party_control
            specialist_review_status:
              enum:
                - required
                - approved
                - preview
            allowed_claim:
              type: string
              minLength: 1
              maxLength: 1000
            prohibited_claims:
              type: array
              maxItems: 30
              uniqueItems: true
              items:
                type: string
                minLength: 1
                maxLength: 300
            engine_requirement:
              type: object
              additionalProperties: false
              required:
                - name
                - flavour
                - minimum_version
              properties:
                name:
                  type: string
                  minLength: 1
                  maxLength: 100
                flavour:
                  type: string
                  minLength: 1
                  maxLength: 100
                minimum_version:
                  type: string
                  minLength: 1
                  maxLength: 100
                exact_version:
                  type: string
                  minLength: 1
                  maxLength: 100
            manifest_sha256:
              $ref: '#/components/schemas/sha256'
            rules_manifest_sha256:
              $ref: '#/components/schemas/sha256'
            mapping_manifest_sha256:
              $ref: '#/components/schemas/sha256'
            mapping_denominator:
              $ref: '#/components/schemas/mappingDenominator-2'
            mapping_approval_status:
              enum:
                - not_obtained
                - approved
            disclaimer:
              type: string
              minLength: 40
              maxLength: 3000
            source_urls:
              type: array
              maxItems: 20
              items:
                type: string
                format: uri
                maxLength: 2048
        mappingDenominator:
          type: object
          additionalProperties: false
          required:
            - total
            - deterministic
            - partial
            - heuristic
            - manual
            - unsupported
            - by_asset_type
          properties:
            total:
              type: integer
              minimum: 0
            deterministic:
              type: integer
              minimum: 0
            partial:
              type: integer
              minimum: 0
            heuristic:
              type: integer
              minimum: 0
            manual:
              type: integer
              minimum: 0
            unsupported:
              type: integer
              minimum: 0
            by_asset_type:
              type: object
              additionalProperties:
                type: integer
                minimum: 0
        rulePack:
          type: object
          additionalProperties: false
          required:
            - id
            - version
            - sha256
          properties:
            id:
              $ref: '#/components/schemas/rulePackId'
            version:
              type: integer
              minimum: 1
            sha256:
              $ref: '#/components/schemas/sha256'
        coverage:
          type: object
          additionalProperties: false
          required:
            - rules_configured
            - rules_executed
            - rules_not_applicable
            - rules_reduced_coverage
            - manual_checks_required
            - automated_failures
            - heuristic_findings
          properties:
            rules_configured:
              $ref: '#/components/schemas/nonNegativeInteger'
            rules_executed:
              $ref: '#/components/schemas/nonNegativeInteger'
            rules_not_applicable:
              $ref: '#/components/schemas/nonNegativeInteger'
            rules_reduced_coverage:
              $ref: '#/components/schemas/nonNegativeInteger'
            manual_checks_required:
              $ref: '#/components/schemas/nonNegativeInteger'
            automated_failures:
              $ref: '#/components/schemas/nonNegativeInteger'
            heuristic_findings:
              $ref: '#/components/schemas/nonNegativeInteger'
        findingSet:
          type: object
          description: Digest of the canonical report findings array; the report artifact contains the complete normalized findings.
          additionalProperties: false
          required:
            - count
            - canonical_json_sha256
          properties:
            count:
              $ref: '#/components/schemas/nonNegativeInteger'
            canonical_json_sha256:
              $ref: '#/components/schemas/sha256'
        nonNegativeInteger:
          type: integer
          minimum: 0
        suppression:
          type: object
          additionalProperties: false
          required:
            - finding_fingerprint
            - actor_id
            - reason
            - created_at
          properties:
            finding_fingerprint:
              type: string
              minLength: 16
              maxLength: 256
            actor_id:
              $ref: '#/components/schemas/actorId'
            reason:
              type: string
              minLength: 1
              maxLength: 1000
            created_at:
              $ref: '#/components/schemas/timestamp'
            expires_at:
              oneOf:
                - $ref: '#/components/schemas/timestamp'
                - type: 'null'
        report:
          type: object
          additionalProperties: false
          required:
            - format
            - schema_version
            - media_type
            - size_bytes
            - sha256
          properties:
            format:
              enum:
                - json
                - html
            schema_version:
              enum:
                - 1.0.0
                - 1.1.0
            media_type:
              enum:
                - application/vnd.accesspreflight.report+json
                - text/html
            size_bytes:
              type: integer
              minimum: 1
              maximum: 104857600
            sha256:
              $ref: '#/components/schemas/sha256'
          allOf:
            - if:
                properties:
                  format:
                    const: json
                required:
                  - format
              then:
                properties:
                  media_type:
                    const: application/vnd.accesspreflight.report+json
            - if:
                properties:
                  format:
                    const: html
                required:
                  - format
              then:
                properties:
                  media_type:
                    const: text/html
        signature:
          type: object
          additionalProperties: false
          required:
            - algorithm
            - canonicalization
            - key_id
            - value
          properties:
            algorithm:
              const: Ed25519-SHA256
            canonicalization:
              const: RFC8785
            key_id:
              type: string
              pattern: ^[A-Za-z0-9._-]{1,100}$
            value:
              description: Base64url-encoded Ed25519 signature over the canonical payload's SHA-256 digest, without padding.
              type: string
              pattern: ^[A-Za-z0-9_-]{86}$
    sha256-2:
      type: string
      pattern: ^[a-f0-9]{64}$
    approval:
      type: object
      additionalProperties: false
      required:
        - status
      properties:
        status:
          enum:
            - not_obtained
            - approved
        reviewer_name:
          type: string
          minLength: 1
          maxLength: 200
        reviewer_organization:
          type: string
          minLength: 1
          maxLength: 200
        reviewed_at:
          type: string
          format: date-time
        evidence_reference:
          type: string
          minLength: 1
          maxLength: 2048
        approved_sha256:
          $ref: '#/components/schemas/sha256-2'
      allOf:
        - if:
            properties:
              status:
                const: approved
            required:
              - status
          then:
            required:
              - reviewer_name
              - reviewer_organization
              - reviewed_at
              - evidence_reference
              - approved_sha256
    source:
      type: object
      additionalProperties: false
      required:
        - id
        - title
        - edition
        - kind
        - official_url
        - verified_at
      properties:
        id:
          type: string
          pattern: ^[a-z0-9]+(?:[._-][a-z0-9]+)*$
        title:
          type: string
          minLength: 1
          maxLength: 300
        edition:
          type: string
          minLength: 1
          maxLength: 100
        kind:
          enum:
            - normative
            - machine_profile
            - legal
            - supplemental
        official_url:
          type: string
          format: uri
          pattern: ^https://
        verified_at:
          type: string
          format: date
    denominator:
      type: object
      additionalProperties: false
      required:
        - total
        - deterministic
        - partial
        - heuristic
        - manual
        - unsupported
        - by_asset_type
      properties:
        total:
          type: integer
          minimum: 1
        deterministic:
          type: integer
          minimum: 0
        partial:
          type: integer
          minimum: 0
        heuristic:
          type: integer
          minimum: 0
        manual:
          type: integer
          minimum: 0
        unsupported:
          type: integer
          minimum: 0
        by_asset_type:
          type: object
          additionalProperties: false
          properties:
            url:
              type: integer
              minimum: 0
            html:
              type: integer
              minimum: 0
            html_bundle:
              type: integer
              minimum: 0
            pdf:
              type: integer
              minimum: 0
            docx:
              type: integer
              minimum: 0
    requirement:
      type: object
      additionalProperties: false
      required:
        - source_id
        - requirement_id
        - short_label
        - asset_type
        - classification
        - implemented_rule_ids
        - rationale
        - limitations
      properties:
        source_id:
          type: string
          pattern: ^[a-z0-9]+(?:[._-][a-z0-9]+)*$
        requirement_id:
          type: string
          minLength: 1
          maxLength: 200
        short_label:
          type: string
          minLength: 1
          maxLength: 300
        asset_type:
          enum:
            - url
            - html
            - html_bundle
            - pdf
            - docx
        classification:
          enum:
            - deterministic
            - partial
            - heuristic
            - manual
            - unsupported
        implemented_rule_ids:
          type: array
          maxItems: 100
          uniqueItems: true
          items:
            type: string
            minLength: 1
            maxLength: 200
        applicability_condition:
          type: object
          additionalProperties: false
          required:
            - declaration
            - operator
            - value
          properties:
            declaration:
              type: string
              minLength: 1
              maxLength: 100
            operator:
              enum:
                - equals
                - not_equals
                - present
            value: {}
        rationale:
          type: string
          minLength: 1
          maxLength: 1000
        limitations:
          type: string
          minLength: 1
          maxLength: 1000
    normative-mapping-v1.schema:
      $schema: https://json-schema.org/draft/2020-12/schema
      $id: https://schemas.accesspreflight.com/normative-mapping-v1.schema.json
      title: AccessPreflight normative transparency mapping v1
      type: object
      additionalProperties: false
      required:
        - schema_version
        - profile_id
        - profile_version
        - mapping_version
        - mapping_status
        - approval
        - normative_sources
        - denominator
        - requirements
      properties:
        schema_version:
          const: normative-mapping.v1
        profile_id:
          type: string
          pattern: ^[a-z0-9]+(?:[._-][a-z0-9]+)*$
        profile_version:
          type: string
          minLength: 1
          maxLength: 40
        mapping_version:
          type: string
          minLength: 1
          maxLength: 100
        mapping_status:
          enum:
            - pending_review
            - approved
        approval:
          $ref: '#/components/schemas/approval'
        normative_sources:
          type: array
          minItems: 1
          maxItems: 30
          items:
            $ref: '#/components/schemas/source'
        denominator:
          $ref: '#/components/schemas/denominator'
        requirements:
          type: array
          minItems: 1
          maxItems: 4000
          items:
            $ref: '#/components/schemas/requirement'
        manifest_sha256:
          description: SHA-256 of the exact source-controlled artifact bytes. Added by the API response and absent from the hashed artifact itself.
          $ref: '#/components/schemas/sha256-2'
      $defs:
        approval:
          type: object
          additionalProperties: false
          required:
            - status
          properties:
            status:
              enum:
                - not_obtained
                - approved
            reviewer_name:
              type: string
              minLength: 1
              maxLength: 200
            reviewer_organization:
              type: string
              minLength: 1
              maxLength: 200
            reviewed_at:
              type: string
              format: date-time
            evidence_reference:
              type: string
              minLength: 1
              maxLength: 2048
            approved_sha256:
              $ref: '#/components/schemas/sha256-2'
          allOf:
            - if:
                properties:
                  status:
                    const: approved
                required:
                  - status
              then:
                required:
                  - reviewer_name
                  - reviewer_organization
                  - reviewed_at
                  - evidence_reference
                  - approved_sha256
        source:
          type: object
          additionalProperties: false
          required:
            - id
            - title
            - edition
            - kind
            - official_url
            - verified_at
          properties:
            id:
              type: string
              pattern: ^[a-z0-9]+(?:[._-][a-z0-9]+)*$
            title:
              type: string
              minLength: 1
              maxLength: 300
            edition:
              type: string
              minLength: 1
              maxLength: 100
            kind:
              enum:
                - normative
                - machine_profile
                - legal
                - supplemental
            official_url:
              type: string
              format: uri
              pattern: ^https://
            verified_at:
              type: string
              format: date
        denominator:
          type: object
          additionalProperties: false
          required:
            - total
            - deterministic
            - partial
            - heuristic
            - manual
            - unsupported
            - by_asset_type
          properties:
            total:
              type: integer
              minimum: 1
            deterministic:
              type: integer
              minimum: 0
            partial:
              type: integer
              minimum: 0
            heuristic:
              type: integer
              minimum: 0
            manual:
              type: integer
              minimum: 0
            unsupported:
              type: integer
              minimum: 0
            by_asset_type:
              type: object
              additionalProperties: false
              properties:
                url:
                  type: integer
                  minimum: 0
                html:
                  type: integer
                  minimum: 0
                html_bundle:
                  type: integer
                  minimum: 0
                pdf:
                  type: integer
                  minimum: 0
                docx:
                  type: integer
                  minimum: 0
        requirement:
          type: object
          additionalProperties: false
          required:
            - source_id
            - requirement_id
            - short_label
            - asset_type
            - classification
            - implemented_rule_ids
            - rationale
            - limitations
          properties:
            source_id:
              type: string
              pattern: ^[a-z0-9]+(?:[._-][a-z0-9]+)*$
            requirement_id:
              type: string
              minLength: 1
              maxLength: 200
            short_label:
              type: string
              minLength: 1
              maxLength: 300
            asset_type:
              enum:
                - url
                - html
                - html_bundle
                - pdf
                - docx
            classification:
              enum:
                - deterministic
                - partial
                - heuristic
                - manual
                - unsupported
            implemented_rule_ids:
              type: array
              maxItems: 100
              uniqueItems: true
              items:
                type: string
                minLength: 1
                maxLength: 200
            applicability_condition:
              type: object
              additionalProperties: false
              required:
                - declaration
                - operator
                - value
              properties:
                declaration:
                  type: string
                  minLength: 1
                  maxLength: 100
                operator:
                  enum:
                    - equals
                    - not_equals
                    - present
                value: {}
            rationale:
              type: string
              minLength: 1
              maxLength: 1000
            limitations:
              type: string
              minLength: 1
              maxLength: 1000
        sha256:
          type: string
          pattern: ^[a-f0-9]{64}$
