{
  "openapi": "3.0.0",
  "info": {
    "title": "CMDash Public API (v1 + v2)",
    "description": "The CMDash Public API for integration and automation. Covers BOTH versions, tagged by path prefix: `/v1/...` operations are tagged `v1 <resource>` and are deprecated-but-unchanged (see docs/developers/public-api-versioning.md); `/v2/...` operations are tagged `v2 <resource>` and are current. Prefer v2 for new integrations — it adds webhooks, reports and audit-logs resources with no v1 equivalent, and fixes v1's two `200 { error: ... }` cases to a real `404`. v1 is scheduled for sunset on Wed, 24 Feb 2027 00:00:00 GMT.",
    "version": "10.2.10",
    "contact": {
      "name": "CMDash Support",
      "url": "https://www.cmdash.com/trust/support"
    },
    "license": {
      "name": "Proprietary"
    }
  },
  "servers": [
    {
      "url": "https://app.cmdash.net/api",
      "description": "Production"
    }
  ],
  "paths": {
    "/v1/compliance/frameworks": {
      "get": {
        "description": "Returns all compliance frameworks with implementation progress metrics. Supported frameworks include CMMC L2, NIST CSF, ISO 27001, SOC 2, PCI DSS, HIPAA, CIS Controls, GDPR, DORA, and FedRAMP.",
        "operationId": "v1_getFrameworks",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Array of frameworks with progress",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ApiEnvelope"
                    },
                    {
                      "type": "object",
                      "required": [
                        "data"
                      ],
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/ComplianceFrameworkDto"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "List compliance frameworks with progress",
        "tags": [
          "v1 compliance"
        ],
        "deprecated": true
      }
    },
    "/v1/compliance/frameworks/{slug}/controls": {
      "get": {
        "description": "Returns all controls for a framework with their current assessment status and notes.",
        "operationId": "v1_getControls",
        "parameters": [
          {
            "name": "slug",
            "required": true,
            "in": "path",
            "description": "Framework slug (e.g., cmmc-l2, nist-csf, iso-27001)",
            "schema": {
              "example": "cmmc-l2",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Framework with controls",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ApiEnvelope"
                    },
                    {
                      "type": "object",
                      "required": [
                        "data"
                      ],
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/FrameworkControlsResponseDto"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Get control statuses for a framework",
        "tags": [
          "v1 compliance"
        ],
        "deprecated": true
      }
    },
    "/v1/compliance/progress": {
      "get": {
        "description": "Returns aggregated compliance progress for all assessed frameworks, showing the number of controls assessed and implemented per framework.",
        "operationId": "v1_getProgress",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Progress across all frameworks",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ApiEnvelope"
                    },
                    {
                      "type": "object",
                      "required": [
                        "data"
                      ],
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/ComplianceProgressDto"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Get compliance progress across all frameworks",
        "tags": [
          "v1 compliance"
        ],
        "deprecated": true
      }
    },
    "/v1/gate/evaluate": {
      "post": {
        "description": "Runs three checks against the tenant's own data — score-drop (a MaturitySnapshot rollup fell more than the threshold since its previous snapshot), control-regression (a control moved backward in status within the lookback window), and expired-exception (an ACTIVE exception is past its expiresAt) — and returns pass/fail plus one cited reason per failure. Every body field is optional and overrides the tenant's saved policy (Settings > Posture Gate) for this call only.",
        "operationId": "v1_evaluate",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Makes the write safe to retry. Re-sending the same key and body replays the first response (it keeps its original `requestId`); the same key with a different body is `422`; the same key while the first call is still running is `409`. 8 to 128 characters of `A-Z a-z 0-9 . _ -`, scoped to your tenant.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9._-]{8,128}$",
              "minLength": 8,
              "maxLength": 128
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GateEvaluateBodyDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Gate evaluation result",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ApiEnvelope"
                    },
                    {
                      "type": "object",
                      "required": [
                        "data"
                      ],
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/GateEvaluationResultDto"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Evaluate the posture-as-code CI gate",
        "tags": [
          "v1 gate"
        ],
        "deprecated": true
      }
    },
    "/v1/gate/policy": {
      "get": {
        "operationId": "v1_getPolicy",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Effective gate policy",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ApiEnvelope"
                    },
                    {
                      "type": "object",
                      "required": [
                        "data"
                      ],
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/GatePolicyDto"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Get the effective posture-gate policy (defaults if never configured)",
        "tags": [
          "v1 gate"
        ],
        "deprecated": true
      }
    },
    "/v1/integrations": {
      "get": {
        "description": "Returns all integration connections for the organization with their sync status. Supported providers include Microsoft Defender, CrowdStrike, Tenable, Splunk, and 13 more.",
        "operationId": "v1_listIntegrations",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Array of integration connections",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ApiEnvelope"
                    },
                    {
                      "type": "object",
                      "required": [
                        "data"
                      ],
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/IntegrationConnectionDto"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "List connected integrations",
        "tags": [
          "v1 integrations"
        ],
        "deprecated": true
      }
    },
    "/v1/integrations/{id}/health": {
      "get": {
        "description": "Returns the integration connection details along with the last 5 sync log entries, including timing data and error messages.",
        "operationId": "v1_getHealth",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "description": "Integration connection UUID",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Integration health with sync logs",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ApiEnvelope"
                    },
                    {
                      "type": "object",
                      "required": [
                        "data"
                      ],
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/IntegrationHealthDto"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "404": {
            "description": "Integration not found"
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Get integration sync status and health",
        "tags": [
          "v1 integrations"
        ],
        "deprecated": true
      }
    },
    "/v1/posture/history": {
      "get": {
        "description": "Returns daily posture score snapshots over the specified time window. Useful for trend analysis and board reporting.",
        "operationId": "v1_getHistory",
        "parameters": [
          {
            "name": "days",
            "required": false,
            "in": "query",
            "description": "Number of days of history (default: 30)",
            "schema": {
              "example": 30,
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Posture history array",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ApiEnvelope"
                    },
                    {
                      "type": "object",
                      "required": [
                        "data"
                      ],
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/PostureHistoryPointDto"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Get posture score history",
        "tags": [
          "v1 posture"
        ],
        "deprecated": true
      }
    },
    "/v1/posture/latest": {
      "get": {
        "description": "Returns the most recent posture score snapshot with all signal values. The score is computed from 5 signals: Mean Time to Patch, MFA Coverage, Open Critical Vulnerabilities, Incident Frequency, and Policy Compliance.",
        "operationId": "v1_getLatest",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Latest posture snapshot",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ApiEnvelope"
                    },
                    {
                      "type": "object",
                      "required": [
                        "data"
                      ],
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/PostureLatestDto"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Get latest posture score",
        "tags": [
          "v1 posture"
        ],
        "deprecated": true
      }
    },
    "/v1/posture/signals": {
      "get": {
        "description": "Returns each posture signal with its raw value, normalized score, weight, and weighted contribution to the overall posture score.",
        "operationId": "v1_getSignals",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Signal breakdown with contributions",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ApiEnvelope"
                    },
                    {
                      "type": "object",
                      "required": [
                        "data"
                      ],
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/PostureSignalBreakdownDto"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Get detailed signal breakdown",
        "tags": [
          "v1 posture"
        ],
        "deprecated": true
      }
    },
    "/v1/risks": {
      "get": {
        "description": "Returns risks sorted by risk score (descending). Supports filtering by status, category, and minimum risk score. Results are paginated.",
        "operationId": "v1_listRisks",
        "parameters": [
          {
            "name": "status",
            "required": false,
            "in": "query",
            "schema": {
              "enum": [
                "OPEN",
                "IN_PROGRESS",
                "MITIGATED",
                "ACCEPTED",
                "CLOSED"
              ],
              "type": "string"
            }
          },
          {
            "name": "category",
            "required": false,
            "in": "query",
            "schema": {
              "example": "Vulnerability Management",
              "type": "string"
            }
          },
          {
            "name": "minScore",
            "required": false,
            "in": "query",
            "schema": {
              "example": 10,
              "type": "number"
            }
          },
          {
            "name": "limit",
            "required": false,
            "in": "query",
            "description": "Max results (default: 50, maximum: 500; a larger value is clamped to 500, a non-numeric value is a 400)",
            "schema": {
              "example": 50,
              "type": "number"
            }
          },
          {
            "name": "offset",
            "required": false,
            "in": "query",
            "description": "Pagination offset",
            "schema": {
              "example": 0,
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated risk list",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ApiEnvelope"
                    },
                    {
                      "type": "object",
                      "required": [
                        "data"
                      ],
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/RiskListResponseDto"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "List risks with optional filtering",
        "tags": [
          "v1 risks"
        ],
        "deprecated": true
      },
      "post": {
        "description": "Creates a new risk entry. A unique risk ID (RSK-001, RSK-002, etc.) is auto-generated. Risk score is computed as likelihood × impact.",
        "operationId": "v1_createRisk",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Makes the write safe to retry. Re-sending the same key and body replays the first response (it keeps its original `requestId`); the same key with a different body is `422`; the same key while the first call is still running is `409`. 8 to 128 characters of `A-Z a-z 0-9 . _ -`, scoped to your tenant.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9._-]{8,128}$",
              "minLength": 8,
              "maxLength": 128
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateRiskBodyDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created risk",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ApiEnvelope"
                    },
                    {
                      "type": "object",
                      "required": [
                        "data"
                      ],
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/RiskItemDto"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Create a new risk",
        "tags": [
          "v1 risks"
        ],
        "deprecated": true
      }
    },
    "/v1/risks/loss-exposure": {
      "get": {
        "description": "Returns the tenant's aggregate FAIR-quantified loss exposure: total annualized loss expectancy (median/P50) plus the P10 (low) and P90 (high) bounds, and a per-risk breakdown sorted by ALE descending. Figures come directly from the Monte Carlo FAIR engine — nothing is estimated or fabricated here. Risks that have never been FAIR-quantified are simply excluded from the breakdown; if the tenant has no FAIR data at all, totals are 0 and `risks` is an empty array.",
        "operationId": "v1_getLossExposureSummary",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Tenant-wide FAIR loss-exposure summary",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ApiEnvelope"
                    },
                    {
                      "type": "object",
                      "required": [
                        "data"
                      ],
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/FairExposureSummaryDto"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Get FAIR loss-exposure summary (ALE + percentiles)",
        "tags": [
          "v1 risks"
        ],
        "deprecated": true
      }
    },
    "/v1/risks/{id}": {
      "put": {
        "description": "Updates an existing risk. If likelihood or impact changes, risk score is recalculated automatically.",
        "operationId": "v1_updateRisk",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "description": "Risk UUID",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Makes the write safe to retry. Re-sending the same key and body replays the first response (it keeps its original `requestId`); the same key with a different body is `422`; the same key while the first call is still running is `409`. 8 to 128 characters of `A-Z a-z 0-9 . _ -`, scoped to your tenant.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9._-]{8,128}$",
              "minLength": 8,
              "maxLength": 128
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateRiskBodyDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated risk",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ApiEnvelope"
                    },
                    {
                      "type": "object",
                      "required": [
                        "data"
                      ],
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/RiskItemDto"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Update a risk",
        "tags": [
          "v1 risks"
        ],
        "deprecated": true
      }
    },
    "/v1/risks/{id}/controls": {
      "get": {
        "description": "Returns compliance controls linked to a specific risk, including framework information.",
        "operationId": "v1_getRiskControls",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "description": "Risk UUID",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Linked controls",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ApiEnvelope"
                    },
                    {
                      "type": "object",
                      "required": [
                        "data"
                      ],
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/RiskControlLinkDto"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Get linked controls for a risk",
        "tags": [
          "v1 risks"
        ],
        "deprecated": true
      }
    },
    "/v1/risks/{id}/loss-exposure": {
      "get": {
        "description": "Returns the latest FAIR quantification (ALE + P10/P90) for one risk. If the risk has never been FAIR-quantified, `available` is false and every numeric field is null — no figure is ever fabricated.",
        "operationId": "v1_getRiskLossExposure",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "description": "Risk UUID",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Loss exposure for the risk (or a not-available shape)",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ApiEnvelope"
                    },
                    {
                      "type": "object",
                      "required": [
                        "data"
                      ],
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/FairLossExposureDto"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Get FAIR loss exposure for a single risk",
        "tags": [
          "v1 risks"
        ],
        "deprecated": true
      }
    },
    "/v2/assets": {
      "get": {
        "description": "Paginated, newest-stable ordering. Read-only; requires the `assets:read` key permission.",
        "operationId": "v2_list",
        "parameters": [
          {
            "name": "assetType",
            "required": false,
            "in": "query",
            "schema": {
              "enum": [
                "SYSTEM",
                "DATA_CLASS",
                "PROCESS"
              ],
              "type": "string"
            }
          },
          {
            "name": "criticality",
            "required": false,
            "in": "query",
            "schema": {
              "enum": [
                "CRITICAL",
                "HIGH",
                "MEDIUM",
                "LOW"
              ],
              "type": "string"
            }
          },
          {
            "name": "limit",
            "required": false,
            "in": "query",
            "description": "Max results (default 50, maximum 500; a larger value is clamped, a non-numeric value is a 400)",
            "schema": {
              "example": 50,
              "type": "number"
            }
          },
          {
            "name": "offset",
            "required": false,
            "in": "query",
            "description": "Rows to skip",
            "schema": {
              "example": 0,
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated assets",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ApiEnvelope"
                    },
                    {
                      "type": "object",
                      "required": [
                        "data"
                      ],
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/PublicAssetListDto"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "List assets",
        "tags": [
          "v2 assets"
        ]
      }
    },
    "/v2/assets/{id}": {
      "get": {
        "description": "Requires the `assets:read` key permission.",
        "operationId": "v2_get",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "description": "Asset UUID",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The asset",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ApiEnvelope"
                    },
                    {
                      "type": "object",
                      "required": [
                        "data"
                      ],
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/PublicAssetDto"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "404": {
            "description": "Asset not found"
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Get one asset by id",
        "tags": [
          "v2 assets"
        ]
      }
    },
    "/v2/audit-logs": {
      "get": {
        "operationId": "v2_list",
        "parameters": [
          {
            "name": "page",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "limit",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "action",
            "required": false,
            "in": "query",
            "description": "Filter by action prefix (e.g. \"user.\", \"integration.\")",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "from",
            "required": false,
            "in": "query",
            "description": "ISO 8601 start date",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "to",
            "required": false,
            "in": "query",
            "description": "ISO 8601 end date",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated audit log list",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ApiEnvelope"
                    },
                    {
                      "type": "object",
                      "required": [
                        "data"
                      ],
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/AuditLogListResponseDto"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "List audit logs with pagination and optional filters",
        "tags": [
          "v2 audit-logs"
        ]
      }
    },
    "/v2/audit-logs/export": {
      "get": {
        "description": "CSV is UTF-8 WITH a byte order mark, CRLF records (RFC 4180) and Title Case headers (ID, Timestamp, User Email, User Name, Action, Resource, IP Address, Metadata) — the format changed in v8.13.0; see docs/developers/public-api-versioning.md \"v2 CSV export format change\". The filename is cmdash-<org-slug>-audit-log-<YYYY-MM-DD>.csv. An empty date range returns 200 with a header row and no data rows (it used to 404). The export is capped at 10,000 rows: X-Total-Count is how many rows match the range, X-Truncated says whether the cap applied, X-Ceiling is the cap, and a capped CSV ends with a trailer row saying so. X-Total-Records (rows in the file) is unchanged.",
        "operationId": "v2_exportLogs",
        "parameters": [
          {
            "name": "format",
            "required": false,
            "in": "query",
            "description": "Export format (default: csv)",
            "schema": {
              "enum": [
                "csv",
                "json"
              ],
              "type": "string"
            }
          },
          {
            "name": "from",
            "required": false,
            "in": "query",
            "description": "ISO 8601 start date (default: 90 days ago)",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "to",
            "required": false,
            "in": "query",
            "description": "ISO 8601 end date (default: now)",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "action",
            "required": false,
            "in": "query",
            "description": "Filter by action prefix",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The export file, NOT wrapped in the `{ success, data }` envelope. `format=csv` (default) is `text/csv`; `format=json` is a bare JSON array of audit-log rows. Both carry `Content-Disposition: attachment` and the `X-Total-Records`, `X-Total-Count`, `X-Truncated` and `X-Ceiling` headers.",
            "headers": {
              "X-Total-Records": {
                "description": "Rows in the file.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Total-Count": {
                "description": "Rows matching the range (JSON and CSV).",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Truncated": {
                "description": "`true` when the 10,000-row cap applied.",
                "schema": {
                  "type": "string",
                  "enum": [
                    "true",
                    "false"
                  ]
                }
              },
              "X-Ceiling": {
                "description": "The row cap.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "text/csv": {
                "schema": {
                  "type": "string"
                }
              },
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "additionalProperties": true
                  }
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Export audit logs as CSV or JSON for external auditors",
        "tags": [
          "v2 audit-logs"
        ],
        "x-cmdash-unenveloped": true
      }
    },
    "/v2/collector/definitions": {
      "get": {
        "operationId": "v2_definitions",
        "parameters": [
          {
            "name": "connectorId",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "COLLECTOR-mode connector definitions",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ApiEnvelope"
                    },
                    {
                      "type": "object",
                      "required": [
                        "data"
                      ],
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/CollectorDefinitionsResponseDto"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Invalid key or signature",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Connector definitions this tenant has marked COLLECTOR-mode. Never includes credentials — a collector supplies those from its own environment.",
        "tags": [
          "v2 collector"
        ]
      }
    },
    "/v2/collector/keys": {
      "post": {
        "operationId": "v2_registerKey",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Makes the write safe to retry. Re-sending the same key and body replays the first response (it keeps its original `requestId`); the same key with a different body is `422`; the same key while the first call is still running is `409`. 8 to 128 characters of `A-Z a-z 0-9 . _ -`, scoped to your tenant.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9._-]{8,128}$",
              "minLength": 8,
              "maxLength": 128
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "enrollmentId",
                  "registrationToken",
                  "publicKey",
                  "proof"
                ],
                "properties": {
                  "enrollmentId": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "registrationToken": {
                    "type": "string"
                  },
                  "publicKey": {
                    "type": "string",
                    "description": "base64url DER SPKI of the Ed25519 public key"
                  },
                  "proof": {
                    "type": "string",
                    "description": "base64url Ed25519 signature: proof of possession (spec §2)"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The registered key and the identity the collector signs as",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ApiEnvelope"
                    },
                    {
                      "type": "object",
                      "required": [
                        "data"
                      ],
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/CollectorKeyRegistrationDto"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Invalid key or signature",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Register the collector’s Ed25519 public key with a one-time token and a proof of possession. The private key never leaves the collector.",
        "tags": [
          "v2 collector"
        ]
      }
    },
    "/v2/collector/keys/rotate": {
      "post": {
        "operationId": "v2_rotateKey",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Makes the write safe to retry. Re-sending the same key and body replays the first response (it keeps its original `requestId`); the same key with a different body is `422`; the same key while the first call is still running is `409`. 8 to 128 characters of `A-Z a-z 0-9 . _ -`, scoped to your tenant.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9._-]{8,128}$",
              "minLength": 8,
              "maxLength": 128
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "enrollmentId",
                  "newPublicKey",
                  "t",
                  "proofOld",
                  "proofNew"
                ],
                "properties": {
                  "enrollmentId": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "newPublicKey": {
                    "type": "string"
                  },
                  "t": {
                    "type": "integer"
                  },
                  "proofOld": {
                    "type": "string"
                  },
                  "proofNew": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The successor key and the identity the collector signs as",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ApiEnvelope"
                    },
                    {
                      "type": "object",
                      "required": [
                        "data"
                      ],
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/CollectorKeyRegistrationDto"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Invalid key or signature",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Replace the collector’s key; the rotation is signed by BOTH the registered key and the new one.",
        "tags": [
          "v2 collector"
        ]
      }
    },
    "/v2/collector/push": {
      "post": {
        "operationId": "v2_push",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Makes the write safe to retry. Re-sending the same key and body replays the first response (it keeps its original `requestId`); the same key with a different body is `422`; the same key while the first call is still running is `409`. 8 to 128 characters of `A-Z a-z 0-9 . _ -`, scoped to your tenant.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9._-]{8,128}$",
              "minLength": 8,
              "maxLength": 128
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CollectorPushBodyDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Counts of what the push ingested",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ApiEnvelope"
                    },
                    {
                      "type": "object",
                      "required": [
                        "data"
                      ],
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/CollectorPushResultDto"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Invalid key or signature",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Push results collected inside your own network. Requires a valid X-CMDash-Collector-Signature.",
        "tags": [
          "v2 collector"
        ]
      }
    },
    "/v2/compliance/frameworks": {
      "get": {
        "description": "Identical to v1.",
        "operationId": "v2_getFrameworks",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Array of frameworks with progress",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ApiEnvelope"
                    },
                    {
                      "type": "object",
                      "required": [
                        "data"
                      ],
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/ComplianceFrameworkDto"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "List compliance frameworks with progress",
        "tags": [
          "v2 compliance"
        ]
      }
    },
    "/v2/compliance/frameworks/{slug}/controls": {
      "get": {
        "description": "Returns all controls for a framework with their current assessment status and notes. v2 change from v1: an unknown slug now returns a real 404 with the standard error envelope instead of v1's `200 { error: 'Framework not found' }` — see docs/developers/public-api-versioning.md.",
        "operationId": "v2_getControls",
        "parameters": [
          {
            "name": "slug",
            "required": true,
            "in": "path",
            "description": "Framework slug (e.g., cmmc-l2, nist-csf, iso-27001)",
            "schema": {
              "example": "cmmc-l2",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Framework with controls",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ApiEnvelope"
                    },
                    {
                      "type": "object",
                      "required": [
                        "data"
                      ],
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/FrameworkControlsResponseDto"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "404": {
            "description": "Framework not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Get control statuses for a framework",
        "tags": [
          "v2 compliance"
        ]
      }
    },
    "/v2/compliance/progress": {
      "get": {
        "description": "Identical to v1.",
        "operationId": "v2_getProgress",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Progress across all frameworks",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ApiEnvelope"
                    },
                    {
                      "type": "object",
                      "required": [
                        "data"
                      ],
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/ComplianceProgressDto"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Get compliance progress across all frameworks",
        "tags": [
          "v2 compliance"
        ]
      }
    },
    "/v2/evidence": {
      "get": {
        "description": "Paginated, newest-stable ordering. Read-only; requires the `evidence:read` key permission.",
        "operationId": "v2_list",
        "parameters": [
          {
            "name": "status",
            "required": false,
            "in": "query",
            "schema": {
              "enum": [
                "PENDING",
                "COLLECTED",
                "REVIEWED",
                "APPROVED",
                "REJECTED"
              ],
              "type": "string"
            }
          },
          {
            "name": "collectionMethod",
            "required": false,
            "in": "query",
            "schema": {
              "enum": [
                "MANUAL",
                "AUTOMATED",
                "INTEGRATION",
                "PROPAGATED"
              ],
              "type": "string"
            }
          },
          {
            "name": "frameworkSlug",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "controlId",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "required": false,
            "in": "query",
            "description": "Max results (default 50, maximum 500; a larger value is clamped, a non-numeric value is a 400)",
            "schema": {
              "example": 50,
              "type": "number"
            }
          },
          {
            "name": "offset",
            "required": false,
            "in": "query",
            "description": "Rows to skip",
            "schema": {
              "example": 0,
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated evidence items",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ApiEnvelope"
                    },
                    {
                      "type": "object",
                      "required": [
                        "data"
                      ],
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/PublicEvidenceListDto"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "List evidence items",
        "tags": [
          "v2 evidence"
        ]
      },
      "post": {
        "description": "Rows are filed with collectionMethod=INTEGRATION and status=COLLECTED — a machine push is an integration signal, not something awaiting human review. Send an Idempotency-Key header to make a retry safe.",
        "operationId": "v2_pushEvidence",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Makes the write safe to retry. Re-sending the same key and body replays the first response (it keeps its original `requestId`); the same key with a different body is `422`; the same key while the first call is still running is `409`. 8 to 128 characters of `A-Z a-z 0-9 . _ -`, scoped to your tenant.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9._-]{8,128}$",
              "minLength": 8,
              "maxLength": 128
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PushEvidenceDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Evidence filed",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ApiEnvelope"
                    },
                    {
                      "type": "object",
                      "required": [
                        "data"
                      ],
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/PushEvidenceResultDto"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "File evidence against controls from your own tooling",
        "tags": [
          "v2 evidence"
        ]
      }
    },
    "/v2/evidence/{id}": {
      "get": {
        "description": "Requires the `evidence:read` key permission.",
        "operationId": "v2_get",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "description": "Evidence UUID",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The evidence",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ApiEnvelope"
                    },
                    {
                      "type": "object",
                      "required": [
                        "data"
                      ],
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/PublicEvidenceDto"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "404": {
            "description": "Evidence not found"
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Get one evidence by id",
        "tags": [
          "v2 evidence"
        ]
      }
    },
    "/v2/gate/evaluate": {
      "post": {
        "description": "Identical to v1 — see /v1/gate/evaluate for the full check description.",
        "operationId": "v2_evaluate",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Makes the write safe to retry. Re-sending the same key and body replays the first response (it keeps its original `requestId`); the same key with a different body is `422`; the same key while the first call is still running is `409`. 8 to 128 characters of `A-Z a-z 0-9 . _ -`, scoped to your tenant.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9._-]{8,128}$",
              "minLength": 8,
              "maxLength": 128
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GateEvaluateBodyDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Gate evaluation result",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ApiEnvelope"
                    },
                    {
                      "type": "object",
                      "required": [
                        "data"
                      ],
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/GateEvaluationResultDto"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Evaluate the posture-as-code CI gate",
        "tags": [
          "v2 gate"
        ]
      }
    },
    "/v2/gate/policy": {
      "get": {
        "description": "Identical to v1.",
        "operationId": "v2_getPolicy",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Effective gate policy",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ApiEnvelope"
                    },
                    {
                      "type": "object",
                      "required": [
                        "data"
                      ],
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/GatePolicyDto"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Get the effective gate policy (defaults if never configured)",
        "tags": [
          "v2 gate"
        ]
      }
    },
    "/v2/incidents": {
      "get": {
        "description": "Paginated, newest-stable ordering. Read-only; requires the `incidents:read` key permission.",
        "operationId": "v2_list",
        "parameters": [
          {
            "name": "status",
            "required": false,
            "in": "query",
            "schema": {
              "enum": [
                "OPEN",
                "CONTAINED",
                "MONITORING",
                "CLOSED"
              ],
              "type": "string"
            }
          },
          {
            "name": "severity",
            "required": false,
            "in": "query",
            "schema": {
              "enum": [
                "CRITICAL",
                "HIGH",
                "MEDIUM",
                "LOW"
              ],
              "type": "string"
            }
          },
          {
            "name": "limit",
            "required": false,
            "in": "query",
            "description": "Max results (default 50, maximum 500; a larger value is clamped, a non-numeric value is a 400)",
            "schema": {
              "example": 50,
              "type": "number"
            }
          },
          {
            "name": "offset",
            "required": false,
            "in": "query",
            "description": "Rows to skip",
            "schema": {
              "example": 0,
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated incidents",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ApiEnvelope"
                    },
                    {
                      "type": "object",
                      "required": [
                        "data"
                      ],
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/PublicIncidentListDto"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "List incidents",
        "tags": [
          "v2 incidents"
        ]
      }
    },
    "/v2/incidents/{id}": {
      "get": {
        "description": "Requires the `incidents:read` key permission.",
        "operationId": "v2_get",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "description": "Incident UUID",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The incident",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ApiEnvelope"
                    },
                    {
                      "type": "object",
                      "required": [
                        "data"
                      ],
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/PublicIncidentDto"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "404": {
            "description": "Incident not found"
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Get one incident by id",
        "tags": [
          "v2 incidents"
        ]
      }
    },
    "/v2/integrations": {
      "get": {
        "description": "Identical to v1.",
        "operationId": "v2_listIntegrations",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Array of integration connections",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ApiEnvelope"
                    },
                    {
                      "type": "object",
                      "required": [
                        "data"
                      ],
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/IntegrationConnectionDto"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "List connected integrations",
        "tags": [
          "v2 integrations"
        ]
      }
    },
    "/v2/integrations/{id}/health": {
      "get": {
        "description": "Returns the integration connection details along with the last 5 sync log entries. v2 change from v1: an unknown/foreign id now returns a real 404 with the standard error envelope instead of v1's `200 { error: 'Integration not found' }` — see docs/developers/public-api-versioning.md.",
        "operationId": "v2_getHealth",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "description": "Integration connection UUID",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Integration health with sync logs",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ApiEnvelope"
                    },
                    {
                      "type": "object",
                      "required": [
                        "data"
                      ],
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/IntegrationHealthDto"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "404": {
            "description": "Integration not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Get integration sync status and health",
        "tags": [
          "v2 integrations"
        ]
      }
    },
    "/v2/integrations/{id}/sync": {
      "post": {
        "description": "v2-only — enforces the previously-unenforced `integrations:write` key scope. Enqueues the same BullMQ sync job the authenticated dashboard's \"Sync now\" button uses. Held to the plan's sync floor (Free: one sync per 24 h, Starter: per 4 h, Essentials and above: per 1 h, on a deployment that enforces it), shared with scheduled syncs: a request inside the window is refused with 429 `SYNC_RATE_LIMITED_BY_TIER` and a `Retry-After` header. `enqueued` is false (still 202) when nothing was queued: a read-only license, or a connection removed mid-request.",
        "operationId": "v2_triggerSync",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "description": "Integration connection UUID",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Makes the write safe to retry. Re-sending the same key and body replays the first response (it keeps its original `requestId`); the same key with a different body is `422`; the same key while the first call is still running is `409`. 8 to 128 characters of `A-Z a-z 0-9 . _ -`, scoped to your tenant.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9._-]{8,128}$",
              "minLength": 8,
              "maxLength": 128
            }
          }
        ],
        "requestBody": {
          "required": false,
          "description": "This operation reads no request body: send none, or `{}`.",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {},
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Sync job enqueued",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ApiEnvelope"
                    },
                    {
                      "type": "object",
                      "required": [
                        "data"
                      ],
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/IntegrationSyncResultDto"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "404": {
            "description": "Integration not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "409": {
            "description": "`SYNC_ORG_NOT_ACTIVE` — the workspace is not ACTIVE (for example SUSPENDED), so it does not sync",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "429": {
            "description": "`SYNC_RATE_LIMITED_BY_TIER` — a sync of this connection was already attempted inside the plan's sync floor. `details.nextSyncAllowedAt` (ISO) and `details.retryAfterSeconds` say when a request will succeed, and the `Retry-After` header carries the same wait. (The per-key API rate limit also answers 429, without that code.)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "503": {
            "description": "`SYNC_FLOOR_UNAVAILABLE` — the sync floor could not be checked, so nothing was queued; retry shortly",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Trigger an on-demand sync for a connection",
        "tags": [
          "v2 integrations"
        ]
      }
    },
    "/v2/observations": {
      "post": {
        "description": "Observations are recorded at the ATTESTED tier and tagged API_PUSH. Upserts on a natural key, so re-pushing the same externalId updates one row rather than accumulating.",
        "operationId": "v2_pushObservations",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Makes the write safe to retry. Re-sending the same key and body replays the first response (it keeps its original `requestId`); the same key with a different body is `422`; the same key while the first call is still running is `409`. 8 to 128 characters of `A-Z a-z 0-9 . _ -`, scoped to your tenant.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9._-]{8,128}$",
              "minLength": 8,
              "maxLength": 128
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PushObservationsDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Observations ingested",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ApiEnvelope"
                    },
                    {
                      "type": "object",
                      "required": [
                        "data"
                      ],
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/PushObservationsResultDto"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Push maturity observations from your own tooling",
        "tags": [
          "v2 observations"
        ]
      }
    },
    "/v2/partner/clients": {
      "get": {
        "description": "Active vCISO client workspaces whose parent is the key's organization, in id order, one page at a time (keyset): pass the previous page's `nextCursor` as `after`; `nextCursor` is null on the last page.",
        "operationId": "v2_listClients",
        "parameters": [
          {
            "name": "after",
            "required": false,
            "in": "query",
            "description": "The `nextCursor` of the previous page",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "required": false,
            "in": "query",
            "description": "Page size, 1 to 200 (default 200)",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "One page of owned client workspaces",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ApiEnvelope"
                    },
                    {
                      "type": "object",
                      "required": [
                        "data"
                      ],
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/PartnerClientsPageDto"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "`after` is not a client id, or `limit` is outside 1 to 200",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "403": {
            "description": "API_KEY_SCOPE_MISMATCH (a TENANT key), PARTNER_PRACTICE_LAPSED (the plan no longer includes the vCISO practice console), or the key lacks the route's partner:* permission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (per parent and client)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "List the client workspaces this practice owns",
        "tags": [
          "v2 partner"
        ]
      }
    },
    "/v2/partner/clients/{clientId}/controls": {
      "get": {
        "description": "One row per assessed control: status after the stale-evidence downgrade, `degraded` when that downgrade applied, and lastEvaluatedAt. At most 1000 rows; filter with `framework`.",
        "operationId": "v2_controls",
        "parameters": [
          {
            "name": "clientId",
            "required": true,
            "in": "path",
            "description": "A client workspace id from GET /v2/partner/clients",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "framework",
            "required": false,
            "in": "query",
            "description": "Framework slug, e.g. soc2",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The client's control assessments (effective status)",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ApiEnvelope"
                    },
                    {
                      "type": "object",
                      "required": [
                        "data"
                      ],
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/PartnerControlsDto"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "403": {
            "description": "API_KEY_SCOPE_MISMATCH (a TENANT key), PARTNER_PRACTICE_LAPSED (the plan no longer includes the vCISO practice console), or the key lacks the route's partner:* permission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "404": {
            "description": "PARTNER_CLIENT_NOT_FOUND",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (per parent and client)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "A client's control assessments (effective status)",
        "tags": [
          "v2 partner"
        ]
      }
    },
    "/v2/partner/clients/{clientId}/posture": {
      "get": {
        "operationId": "v2_posture",
        "parameters": [
          {
            "name": "clientId",
            "required": true,
            "in": "path",
            "description": "A client workspace id from GET /v2/partner/clients",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The client's latest posture, or `posture: null` when it has none",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ApiEnvelope"
                    },
                    {
                      "type": "object",
                      "required": [
                        "data"
                      ],
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/PartnerPostureDto"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "403": {
            "description": "API_KEY_SCOPE_MISMATCH (a TENANT key), PARTNER_PRACTICE_LAPSED (the plan no longer includes the vCISO practice console), or the key lacks the route's partner:* permission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "404": {
            "description": "PARTNER_CLIENT_NOT_FOUND — the id is not a workspace this practice owns today (same body whatever the reason)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (per parent and client)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "A client's latest posture score and signals",
        "tags": [
          "v2 partner"
        ]
      }
    },
    "/v2/partner/clients/{clientId}/receipts": {
      "get": {
        "description": "Newest first, at most 200. Never the receipt payload or evidence: verify a receipt with the `verifyCommand`, which fetches it from the issuing deployment.",
        "operationId": "v2_receipts",
        "parameters": [
          {
            "name": "clientId",
            "required": true,
            "in": "path",
            "description": "A client workspace id from GET /v2/partner/clients",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The client's proof receipts (ids, digest, status and times only)",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ApiEnvelope"
                    },
                    {
                      "type": "object",
                      "required": [
                        "data"
                      ],
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/PartnerReceiptsDto"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "403": {
            "description": "API_KEY_SCOPE_MISMATCH (a TENANT key), PARTNER_PRACTICE_LAPSED (the plan no longer includes the vCISO practice console), or the key lacks the route's partner:* permission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "404": {
            "description": "PARTNER_CLIENT_NOT_FOUND",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (per parent and client)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "A client's proof receipts: ids, digests and verify commands",
        "tags": [
          "v2 partner"
        ]
      }
    },
    "/v2/partner/clients/{clientId}/remediation": {
      "get": {
        "description": "Tasks not CLOSED, earliest due date first, with an `overdue` flag. At most 200.",
        "operationId": "v2_remediation",
        "parameters": [
          {
            "name": "clientId",
            "required": true,
            "in": "path",
            "description": "A client workspace id from GET /v2/partner/clients",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The client's open remediation tasks",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ApiEnvelope"
                    },
                    {
                      "type": "object",
                      "required": [
                        "data"
                      ],
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/PartnerRemediationDto"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "403": {
            "description": "API_KEY_SCOPE_MISMATCH (a TENANT key), PARTNER_PRACTICE_LAPSED (the plan no longer includes the vCISO practice console), or the key lacks the route's partner:* permission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "404": {
            "description": "PARTNER_CLIENT_NOT_FOUND",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (per parent and client)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "A client's open remediation tasks",
        "tags": [
          "v2 partner"
        ]
      }
    },
    "/v2/policies": {
      "get": {
        "description": "Paginated, newest-stable ordering. Read-only; requires the `policies:read` key permission.",
        "operationId": "v2_list",
        "parameters": [
          {
            "name": "status",
            "required": false,
            "in": "query",
            "schema": {
              "enum": [
                "DRAFT",
                "ACTIVE",
                "RETIRED",
                "IN_REVIEW"
              ],
              "type": "string"
            }
          },
          {
            "name": "category",
            "required": false,
            "in": "query",
            "schema": {
              "enum": [
                "SECURITY",
                "PRIVACY",
                "OPERATIONAL",
                "HR",
                "INCIDENT_RESPONSE",
                "BCDR",
                "COMPLIANCE",
                "GENERAL",
                "GOVERNANCE_APPOINTMENT"
              ],
              "type": "string"
            }
          },
          {
            "name": "limit",
            "required": false,
            "in": "query",
            "description": "Max results (default 50, maximum 500; a larger value is clamped, a non-numeric value is a 400)",
            "schema": {
              "example": 50,
              "type": "number"
            }
          },
          {
            "name": "offset",
            "required": false,
            "in": "query",
            "description": "Rows to skip",
            "schema": {
              "example": 0,
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated policies",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ApiEnvelope"
                    },
                    {
                      "type": "object",
                      "required": [
                        "data"
                      ],
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/PublicPolicyListDto"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "List policies",
        "tags": [
          "v2 policies"
        ]
      }
    },
    "/v2/policies/{id}": {
      "get": {
        "description": "Requires the `policies:read` key permission.",
        "operationId": "v2_get",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "description": "Policy UUID",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The policy",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ApiEnvelope"
                    },
                    {
                      "type": "object",
                      "required": [
                        "data"
                      ],
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/PublicPolicyDetailDto"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "404": {
            "description": "Policy not found"
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Get one policy by id",
        "tags": [
          "v2 policies"
        ]
      }
    },
    "/v2/posture/history": {
      "get": {
        "description": "Returns daily posture score snapshots over the specified time window. Identical to v1.",
        "operationId": "v2_getHistory",
        "parameters": [
          {
            "name": "days",
            "required": false,
            "in": "query",
            "description": "Number of days of history (default: 30)",
            "schema": {
              "example": 30,
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Posture history array",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ApiEnvelope"
                    },
                    {
                      "type": "object",
                      "required": [
                        "data"
                      ],
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/PostureHistoryPointDto"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Get posture score history",
        "tags": [
          "v2 posture"
        ]
      }
    },
    "/v2/posture/latest": {
      "get": {
        "description": "Returns the most recent posture score snapshot with all signal values. Identical to v1.",
        "operationId": "v2_getLatest",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Latest posture snapshot",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ApiEnvelope"
                    },
                    {
                      "type": "object",
                      "required": [
                        "data"
                      ],
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/PostureLatestDto"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Get latest posture score",
        "tags": [
          "v2 posture"
        ]
      }
    },
    "/v2/posture/signals": {
      "get": {
        "description": "Returns each posture signal with its raw value, normalized score, weight, and weighted contribution. Identical to v1.",
        "operationId": "v2_getSignals",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Signal breakdown with contributions",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ApiEnvelope"
                    },
                    {
                      "type": "object",
                      "required": [
                        "data"
                      ],
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/PostureSignalBreakdownDto"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Get detailed signal breakdown",
        "tags": [
          "v2 posture"
        ]
      }
    },
    "/v2/reports": {
      "get": {
        "operationId": "v2_list",
        "parameters": [
          {
            "name": "limit",
            "required": false,
            "in": "query",
            "schema": {
              "example": 20,
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Array of generated reports",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ApiEnvelope"
                    },
                    {
                      "type": "object",
                      "required": [
                        "data"
                      ],
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/GeneratedReportSummaryDto"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "List generated reports",
        "tags": [
          "v2 reports"
        ]
      }
    },
    "/v2/reports/generate": {
      "post": {
        "operationId": "v2_generate",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Makes the write safe to retry. Re-sending the same key and body replays the first response (it keeps its original `requestId`); the same key with a different body is `422`; the same key while the first call is still running is `409`. 8 to 128 characters of `A-Z a-z 0-9 . _ -`, scoped to your tenant.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9._-]{8,128}$",
              "minLength": 8,
              "maxLength": 128
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GenerateReportBodyDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Generated report",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ApiEnvelope"
                    },
                    {
                      "type": "object",
                      "required": [
                        "data"
                      ],
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/GeneratedReportSummaryDto"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Generate a report from a template or ad-hoc sections",
        "tags": [
          "v2 reports"
        ]
      }
    },
    "/v2/reports/{id}": {
      "get": {
        "description": "Always 200 while the report exists. `status` is one of PENDING | PROCESSING | COMPLETE | FAILED; `content` is the assembled report JSON once `ready` is true and null before that (not a rendered PDF binary — use the dashboard download for that). Poll every `retryAfterSeconds` seconds until `terminal` is true.",
        "operationId": "v2_getContent",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "description": "Generated report UUID",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Report job status, with content once ready",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ApiEnvelope"
                    },
                    {
                      "type": "object",
                      "required": [
                        "data"
                      ],
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/ReportContentDto"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "404": {
            "description": "Report not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Get a generated report status and content",
        "tags": [
          "v2 reports"
        ]
      }
    },
    "/v2/risks": {
      "get": {
        "description": "Returns risks sorted by risk score (descending). Identical to v1.",
        "operationId": "v2_listRisks",
        "parameters": [
          {
            "name": "status",
            "required": false,
            "in": "query",
            "schema": {
              "enum": [
                "OPEN",
                "IN_PROGRESS",
                "MITIGATED",
                "ACCEPTED",
                "CLOSED"
              ],
              "type": "string"
            }
          },
          {
            "name": "category",
            "required": false,
            "in": "query",
            "schema": {
              "example": "Vulnerability Management",
              "type": "string"
            }
          },
          {
            "name": "minScore",
            "required": false,
            "in": "query",
            "schema": {
              "example": 10,
              "type": "number"
            }
          },
          {
            "name": "limit",
            "required": false,
            "in": "query",
            "description": "Max results (default: 50, maximum: 500; a larger value is clamped to 500, a non-numeric value is a 400)",
            "schema": {
              "example": 50,
              "type": "number"
            }
          },
          {
            "name": "offset",
            "required": false,
            "in": "query",
            "description": "Pagination offset",
            "schema": {
              "example": 0,
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated risk list",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ApiEnvelope"
                    },
                    {
                      "type": "object",
                      "required": [
                        "data"
                      ],
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/RiskListResponseDto"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "List risks with optional filtering",
        "tags": [
          "v2 risks"
        ]
      },
      "post": {
        "description": "Identical to v1.",
        "operationId": "v2_createRisk",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Makes the write safe to retry. Re-sending the same key and body replays the first response (it keeps its original `requestId`); the same key with a different body is `422`; the same key while the first call is still running is `409`. 8 to 128 characters of `A-Z a-z 0-9 . _ -`, scoped to your tenant.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9._-]{8,128}$",
              "minLength": 8,
              "maxLength": 128
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateRiskBodyDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created risk",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ApiEnvelope"
                    },
                    {
                      "type": "object",
                      "required": [
                        "data"
                      ],
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/RiskItemDto"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Create a new risk",
        "tags": [
          "v2 risks"
        ]
      }
    },
    "/v2/risks/loss-exposure": {
      "get": {
        "description": "Identical to v1.",
        "operationId": "v2_getLossExposureSummary",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Tenant-wide FAIR loss-exposure summary",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ApiEnvelope"
                    },
                    {
                      "type": "object",
                      "required": [
                        "data"
                      ],
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/FairExposureSummaryDto"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Get FAIR loss-exposure summary (ALE + percentiles)",
        "tags": [
          "v2 risks"
        ]
      }
    },
    "/v2/risks/{id}": {
      "put": {
        "description": "Identical to v1.",
        "operationId": "v2_updateRisk",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "description": "Risk UUID",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Makes the write safe to retry. Re-sending the same key and body replays the first response (it keeps its original `requestId`); the same key with a different body is `422`; the same key while the first call is still running is `409`. 8 to 128 characters of `A-Z a-z 0-9 . _ -`, scoped to your tenant.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9._-]{8,128}$",
              "minLength": 8,
              "maxLength": 128
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateRiskBodyDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated risk",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ApiEnvelope"
                    },
                    {
                      "type": "object",
                      "required": [
                        "data"
                      ],
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/RiskItemDto"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "404": {
            "description": "Risk not found"
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Update a risk",
        "tags": [
          "v2 risks"
        ]
      }
    },
    "/v2/risks/{id}/controls": {
      "get": {
        "description": "Identical to v1.",
        "operationId": "v2_getRiskControls",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "description": "Risk UUID",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Linked controls",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ApiEnvelope"
                    },
                    {
                      "type": "object",
                      "required": [
                        "data"
                      ],
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/RiskControlLinkDto"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "404": {
            "description": "Risk not found"
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Get linked controls for a risk",
        "tags": [
          "v2 risks"
        ]
      }
    },
    "/v2/risks/{id}/loss-exposure": {
      "get": {
        "description": "Identical to v1.",
        "operationId": "v2_getRiskLossExposure",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "description": "Risk UUID",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Loss exposure for the risk (or a not-available shape)",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ApiEnvelope"
                    },
                    {
                      "type": "object",
                      "required": [
                        "data"
                      ],
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/FairLossExposureDto"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "404": {
            "description": "Risk not found"
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Get FAIR loss exposure for a single risk",
        "tags": [
          "v2 risks"
        ]
      }
    },
    "/v2/vendors": {
      "get": {
        "description": "Paginated, newest-stable ordering. Read-only; requires the `vendors:read` key permission.",
        "operationId": "v2_list",
        "parameters": [
          {
            "name": "status",
            "required": false,
            "in": "query",
            "schema": {
              "enum": [
                "ONBOARDING",
                "ACTIVE",
                "UNDER_REVIEW",
                "OFFBOARDING",
                "RETIRED"
              ],
              "type": "string"
            }
          },
          {
            "name": "tier",
            "required": false,
            "in": "query",
            "schema": {
              "enum": [
                "T1",
                "T2",
                "T3",
                "T4"
              ],
              "type": "string"
            }
          },
          {
            "name": "criticality",
            "required": false,
            "in": "query",
            "schema": {
              "enum": [
                "CRITICAL",
                "HIGH",
                "MODERATE",
                "LOW"
              ],
              "type": "string"
            }
          },
          {
            "name": "limit",
            "required": false,
            "in": "query",
            "description": "Max results (default 50, maximum 500; a larger value is clamped, a non-numeric value is a 400)",
            "schema": {
              "example": 50,
              "type": "number"
            }
          },
          {
            "name": "offset",
            "required": false,
            "in": "query",
            "description": "Rows to skip",
            "schema": {
              "example": 0,
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated vendors",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ApiEnvelope"
                    },
                    {
                      "type": "object",
                      "required": [
                        "data"
                      ],
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/PublicVendorListDto"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "List vendors",
        "tags": [
          "v2 vendors"
        ]
      }
    },
    "/v2/vendors/{id}": {
      "get": {
        "description": "Requires the `vendors:read` key permission.",
        "operationId": "v2_get",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "description": "Vendor UUID",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The vendor",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ApiEnvelope"
                    },
                    {
                      "type": "object",
                      "required": [
                        "data"
                      ],
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/PublicVendorDto"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "404": {
            "description": "Vendor not found"
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Get one vendor by id",
        "tags": [
          "v2 vendors"
        ]
      }
    },
    "/v2/webhooks": {
      "get": {
        "operationId": "v2_list",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Array of webhook registrations",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ApiEnvelope"
                    },
                    {
                      "type": "object",
                      "required": [
                        "data"
                      ],
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/WebhookListItemDto"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "List all webhook registrations for the tenant",
        "tags": [
          "v2 webhooks"
        ]
      },
      "post": {
        "description": "The HMAC signing secret is returned in plaintext exactly once, in this response.",
        "operationId": "v2_create",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Makes the write safe to retry. Re-sending the same key and body replays the first response (it keeps its original `requestId`); the same key with a different body is `422`; the same key while the first call is still running is `409`. 8 to 128 characters of `A-Z a-z 0-9 . _ -`, scoped to your tenant.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9._-]{8,128}$",
              "minLength": 8,
              "maxLength": 128
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateWebhookBodyDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created webhook (includes the one-time plaintext secret)",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ApiEnvelope"
                    },
                    {
                      "type": "object",
                      "required": [
                        "data"
                      ],
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/WebhookCreateResultDto"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Register a new webhook endpoint",
        "tags": [
          "v2 webhooks"
        ]
      }
    },
    "/v2/webhooks/{id}": {
      "put": {
        "operationId": "v2_update",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "description": "Webhook registration UUID",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Makes the write safe to retry. Re-sending the same key and body replays the first response (it keeps its original `requestId`); the same key with a different body is `422`; the same key while the first call is still running is `409`. 8 to 128 characters of `A-Z a-z 0-9 . _ -`, scoped to your tenant.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9._-]{8,128}$",
              "minLength": 8,
              "maxLength": 128
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateWebhookBodyDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated webhook",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ApiEnvelope"
                    },
                    {
                      "type": "object",
                      "required": [
                        "data"
                      ],
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/WebhookListItemDto"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "404": {
            "description": "Webhook not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Update a webhook registration",
        "tags": [
          "v2 webhooks"
        ]
      },
      "delete": {
        "operationId": "v2_remove",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "description": "Webhook registration UUID",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Makes the write safe to retry. Re-sending the same key and body replays the first response (it keeps its original `requestId`); the same key with a different body is `422`; the same key while the first call is still running is `409`. 8 to 128 characters of `A-Z a-z 0-9 . _ -`, scoped to your tenant.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9._-]{8,128}$",
              "minLength": 8,
              "maxLength": 128
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ApiEnvelope"
                    },
                    {
                      "type": "object",
                      "required": [
                        "data"
                      ],
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/WebhookDeleteResultDto"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "404": {
            "description": "Webhook not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Delete a webhook registration",
        "tags": [
          "v2 webhooks"
        ]
      }
    },
    "/v2/webhooks/{id}/logs": {
      "get": {
        "operationId": "v2_getLogs",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "description": "Webhook registration UUID",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "required": false,
            "in": "query",
            "schema": {
              "example": 50,
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Array of delivery log entries",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ApiEnvelope"
                    },
                    {
                      "type": "object",
                      "required": [
                        "data"
                      ],
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/WebhookDeliveryLogDto"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "404": {
            "description": "Webhook not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Get delivery logs for a webhook",
        "tags": [
          "v2 webhooks"
        ]
      }
    },
    "/v2/webhooks/{id}/rotate-secret": {
      "post": {
        "description": "The previous secret keeps signing deliveries alongside the new one for the overlap window (default 24h, max 168h). The new plaintext secret is returned once here.",
        "operationId": "v2_rotateSecret",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "description": "Webhook registration UUID",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "overlapHours",
            "required": false,
            "in": "query",
            "schema": {
              "example": 24,
              "type": "number"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Makes the write safe to retry. Re-sending the same key and body replays the first response (it keeps its original `requestId`); the same key with a different body is `422`; the same key while the first call is still running is `409`. 8 to 128 characters of `A-Z a-z 0-9 . _ -`, scoped to your tenant.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9._-]{8,128}$",
              "minLength": 8,
              "maxLength": 128
            }
          }
        ],
        "requestBody": {
          "required": false,
          "description": "This operation reads no request body: send none, or `{}`.",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {},
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Rotation result (includes the one-time plaintext secret)",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ApiEnvelope"
                    },
                    {
                      "type": "object",
                      "required": [
                        "data"
                      ],
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/WebhookRotateSecretResultDto"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "404": {
            "description": "Webhook not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Rotate the webhook HMAC signing secret",
        "tags": [
          "v2 webhooks"
        ]
      }
    },
    "/v2/webhooks/{id}/test": {
      "post": {
        "description": "ROADMAP-V100 I13 — same HMAC signing and transport as a live delivery, but marked `test: true` in the signed body and the `X-CMDash-Test` header so a receiver can never mistake it for a real compliance event. Not queued, sequenced, retried or dead-lettered, and does not affect the circuit breaker — verify a new endpoint before any real event reaches it.",
        "operationId": "v2_test",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "description": "Webhook registration UUID",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Makes the write safe to retry. Re-sending the same key and body replays the first response (it keeps its original `requestId`); the same key with a different body is `422`; the same key while the first call is still running is `409`. 8 to 128 characters of `A-Z a-z 0-9 . _ -`, scoped to your tenant.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9._-]{8,128}$",
              "minLength": 8,
              "maxLength": 128
            }
          }
        ],
        "requestBody": {
          "required": false,
          "description": "This operation reads no request body: send none, or `{}`.",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {},
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Test delivery result (success, HTTP status, latency, truncated response body)",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ApiEnvelope"
                    },
                    {
                      "type": "object",
                      "required": [
                        "data"
                      ],
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/WebhookTestResultDto"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "404": {
            "description": "Webhook not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponseDto"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitErrorResponseDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Send a single synthetic test event through the real delivery path",
        "tags": [
          "v2 webhooks"
        ]
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "API key"
      }
    },
    "schemas": {
      "ApiEnvelope": {
        "type": "object",
        "description": "The wrapper around every successful JSON response (`ResponseInterceptor`). The operation-specific payload is `data`. An error response is not this shape: see `ApiErrorResponseDto`.",
        "required": [
          "success",
          "requestId",
          "timestamp"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "enum": [
              true
            ],
            "example": true
          },
          "requestId": {
            "type": "string",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "description": "Also sent as the `X-Request-Id` header; quote it when reporting a problem."
          },
          "timestamp": {
            "type": "string",
            "format": "date-time",
            "example": "2026-10-09T12:00:00.000Z"
          },
          "meta": {
            "type": "object",
            "description": "Present only when the response read a server-capped list. `lists` has one entry per capped list; the flat `hasMore`/`totalCount`/`returned` fields appear only when exactly one list was read. `hasMore: false` is a positive statement that `data` is complete.",
            "required": [
              "lists"
            ],
            "properties": {
              "lists": {
                "type": "array",
                "items": {
                  "type": "object",
                  "required": [
                    "surface",
                    "cap",
                    "returned",
                    "hasMore",
                    "totalCount"
                  ],
                  "properties": {
                    "surface": {
                      "type": "string",
                      "example": "webhooks"
                    },
                    "cap": {
                      "type": "integer",
                      "example": 500
                    },
                    "returned": {
                      "type": "integer",
                      "example": 500
                    },
                    "hasMore": {
                      "type": "boolean"
                    },
                    "totalCount": {
                      "type": "integer",
                      "nullable": true
                    }
                  }
                }
              },
              "hasMore": {
                "type": "boolean"
              },
              "totalCount": {
                "type": "integer",
                "nullable": true
              },
              "returned": {
                "type": "integer"
              }
            }
          }
        }
      },
      "ApiErrorResponseDto": {
        "type": "object",
        "properties": {
          "success": {
            "type": "boolean",
            "example": false
          },
          "requestId": {
            "type": "string",
            "example": "550e8400-e29b-41d4-a716-446655440000"
          },
          "timestamp": {
            "type": "string",
            "example": "2026-03-31T12:00:00.000Z"
          },
          "statusCode": {
            "type": "number",
            "example": 401
          },
          "error": {
            "type": "string",
            "example": "Unauthorized"
          },
          "message": {
            "type": "string",
            "example": "Invalid or expired API key"
          }
        },
        "required": [
          "success",
          "requestId",
          "timestamp",
          "statusCode",
          "error",
          "message"
        ]
      },
      "AuditLogEntryDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "log-uuid-001"
          },
          "userEmail": {
            "type": "object",
            "nullable": true
          },
          "action": {
            "type": "string",
            "example": "integration.connected"
          },
          "resource": {
            "type": "object",
            "nullable": true
          },
          "ipAddress": {
            "type": "object",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "example": "2026-08-24T12:00:00.000Z"
          }
        },
        "required": [
          "id",
          "action",
          "createdAt"
        ]
      },
      "AuditLogListResponseDto": {
        "type": "object",
        "properties": {
          "logs": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AuditLogEntryDto"
            }
          },
          "total": {
            "type": "number",
            "example": 214
          }
        },
        "required": [
          "logs",
          "total"
        ]
      },
      "CollectorConnectorDefinitionDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "cc-1"
          },
          "name": {
            "type": "string",
            "example": "Internal patch API"
          },
          "endpointUrl": {
            "type": "string",
            "example": "https://internal.corp.local/api/patches"
          },
          "httpMethod": {
            "type": "string",
            "example": "GET"
          },
          "requestHeaders": {
            "type": "object",
            "nullable": true,
            "additionalProperties": {
              "type": "string"
            },
            "description": "Non-secret request headers only."
          },
          "requestBody": {
            "type": "string",
            "nullable": true
          },
          "authType": {
            "type": "string",
            "example": "NONE",
            "description": "How the collector authenticates to the target; the credential comes from its own environment."
          },
          "fieldMappings": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CollectorFieldMappingDto"
            }
          }
        },
        "required": [
          "id",
          "name",
          "endpointUrl",
          "httpMethod",
          "requestHeaders",
          "requestBody",
          "authType",
          "fieldMappings"
        ]
      },
      "CollectorDefinitionsResponseDto": {
        "type": "object",
        "properties": {
          "definitions": {
            "description": "COLLECTOR-mode definitions only; at most 200. Never carries a credential.",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CollectorConnectorDefinitionDto"
            }
          }
        },
        "required": [
          "definitions"
        ]
      },
      "CollectorFieldMappingDto": {
        "type": "object",
        "properties": {
          "jsonPath": {
            "type": "string",
            "example": "$.data.compliant",
            "description": "JSONPath into the target's response."
          },
          "targetField": {
            "type": "string",
            "example": "compliant"
          },
          "target": {
            "type": "string",
            "example": "OBSERVATION"
          },
          "observationType": {
            "type": "string",
            "example": "CSPM_CONTROL_STATE"
          }
        },
        "required": [
          "jsonPath",
          "targetField",
          "target"
        ]
      },
      "CollectorKeyRegistrationDto": {
        "type": "object",
        "properties": {
          "collectorKeyId": {
            "type": "string",
            "example": "cmdash-collector-0123456789abcdef",
            "description": "Derived from the registered public key."
          },
          "deployment": {
            "type": "string",
            "example": "https://app.cmdash.example",
            "description": "The deployment origin the collector signs as."
          },
          "tenantRef": {
            "type": "string",
            "example": "tr_0123456789abcdef0123456789abcdef",
            "description": "The tenant reference the collector signs as."
          }
        },
        "required": [
          "collectorKeyId",
          "deployment",
          "tenantRef"
        ]
      },
      "CollectorPushBodyDto": {
        "type": "object",
        "properties": {
          "connectorId": {
            "type": "string",
            "example": "conn-uuid-001",
            "description": "The connector definition these results came from."
          },
          "collectedAt": {
            "type": "string",
            "example": "2026-03-31T12:00:00.000Z",
            "description": "ISO 8601; defaults to receipt time."
          },
          "mapped": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CollectorPushMappedResultDto"
            }
          },
          "attestations": {
            "description": "ROADMAP-BLUE-OCEAN K1 — attested measurements, each { statement, sig, states }: base64url of the canonical statement bytes, base64url of the Ed25519 signature by the collector key, and one MET / NOT_MET / UNKNOWN per item (docs/specs/collector-attestation-v0.md). Accepted only in a push whose v2 signature verified; all or nothing.",
            "type": "array",
            "items": {
              "type": "object"
            }
          }
        }
      },
      "CollectorPushMappedResultDto": {
        "type": "object",
        "properties": {
          "targetField": {
            "type": "string",
            "example": "mfa_enforced",
            "description": "The connector-definition field this result fills."
          },
          "target": {
            "type": "string",
            "example": "OBSERVATION",
            "description": "Where the value lands. Only OBSERVATION is ingested today."
          },
          "observationType": {
            "type": "string",
            "example": "IDENTITY",
            "description": "Required when target is OBSERVATION."
          },
          "values": {
            "example": [
              true
            ],
            "description": "Collected values; a single-element array is unwrapped to a scalar.",
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        },
        "required": [
          "targetField",
          "target",
          "values"
        ]
      },
      "CollectorPushResultDto": {
        "type": "object",
        "properties": {
          "ingested": {
            "type": "number",
            "example": 1,
            "description": "OBSERVATION mappings ingested."
          },
          "enrollmentId": {
            "type": "string",
            "example": "enr-1",
            "description": "The collector enrollment that signed the push."
          },
          "signatureVersion": {
            "type": "number",
            "example": 2,
            "enum": [
              1,
              2
            ],
            "description": "Which signature scheme verified (1 = HMAC, 2 = Ed25519 attested)."
          },
          "attestedMeasurements": {
            "type": "number",
            "example": 0,
            "description": "Attested measurements stored from this push."
          }
        },
        "required": [
          "ingested",
          "enrollmentId",
          "signatureVersion",
          "attestedMeasurements"
        ]
      },
      "ComplianceFrameworkDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "fw-uuid-001"
          },
          "slug": {
            "type": "string",
            "example": "cmmc-l2",
            "description": "URL-safe framework identifier"
          },
          "name": {
            "type": "string",
            "example": "CMMC Level 2"
          },
          "version": {
            "type": "string",
            "example": "2.0"
          },
          "totalControls": {
            "type": "number",
            "example": 110
          },
          "implemented": {
            "type": "number",
            "example": 42
          },
          "inProgress": {
            "type": "number",
            "example": 18
          },
          "notStarted": {
            "type": "number",
            "example": 45
          },
          "notApplicable": {
            "type": "number",
            "example": 5
          },
          "progressPercent": {
            "type": "number",
            "example": 40,
            "description": "Percentage of applicable controls implemented"
          }
        },
        "required": [
          "id",
          "slug",
          "name",
          "totalControls",
          "implemented",
          "inProgress",
          "notStarted",
          "notApplicable",
          "progressPercent"
        ]
      },
      "ComplianceProgressDto": {
        "type": "object",
        "properties": {
          "slug": {
            "type": "string",
            "example": "cmmc-l2"
          },
          "name": {
            "type": "string",
            "example": "CMMC Level 2"
          },
          "assessed": {
            "type": "number",
            "example": 65,
            "description": "Total controls assessed"
          },
          "implemented": {
            "type": "number",
            "example": 42
          },
          "progressPercent": {
            "type": "number",
            "example": 65
          }
        },
        "required": [
          "slug",
          "name",
          "assessed",
          "implemented",
          "progressPercent"
        ]
      },
      "ControlStatusDto": {
        "type": "object",
        "properties": {
          "controlId": {
            "type": "string",
            "example": "AC.L2-3.1.1"
          },
          "domain": {
            "type": "string",
            "example": "Access Control"
          },
          "title": {
            "type": "string",
            "example": "Limit system access to authorized users"
          },
          "status": {
            "type": "string",
            "example": "IMPLEMENTED",
            "enum": [
              "NOT_STARTED",
              "IN_PROGRESS",
              "IMPLEMENTED",
              "NOT_APPLICABLE"
            ]
          },
          "notes": {
            "type": "object",
            "example": "Implemented via Okta SSO + MFA policy",
            "nullable": true
          }
        },
        "required": [
          "controlId",
          "domain",
          "title",
          "status"
        ]
      },
      "CreateRiskBodyDto": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string",
            "minLength": 1,
            "maxLength": 300,
            "example": "Unpatched critical vulnerabilities in production"
          },
          "description": {
            "type": "string",
            "maxLength": 5000,
            "example": "Multiple CVEs with CVSS > 9.0 remain unpatched"
          },
          "category": {
            "type": "string",
            "maxLength": 200,
            "example": "Vulnerability Management"
          },
          "likelihood": {
            "type": "integer",
            "minimum": 1,
            "maximum": 5,
            "example": 4,
            "description": "Likelihood on the 1–5 ordinal scale"
          },
          "impact": {
            "type": "integer",
            "minimum": 1,
            "maximum": 5,
            "example": 5,
            "description": "Impact on the 1–5 ordinal scale"
          },
          "status": {
            "type": "string",
            "enum": [
              "OPEN",
              "IN_PROGRESS",
              "MITIGATED",
              "ACCEPTED",
              "CLOSED"
            ],
            "example": "OPEN"
          },
          "dueDate": {
            "type": "string",
            "maxLength": 50,
            "example": "2026-06-30"
          },
          "notes": {
            "type": "string",
            "maxLength": 5000,
            "example": "Needs urgent remediation"
          }
        },
        "required": [
          "title",
          "likelihood",
          "impact"
        ]
      },
      "CreateWebhookBodyDto": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "minLength": 1,
            "maxLength": 2048,
            "example": "https://example.com/webhooks/cmdash"
          },
          "events": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "approval.request.completed",
                "posture.changed",
                "risk.created",
                "risk.updated",
                "compliance.milestone",
                "integration.synced",
                "integration.failed",
                "alert.triggered",
                "evidence.propagated",
                "webhook.registration.circuit_opened",
                "evidence.linked",
                "evidence.unlinked",
                "control.created",
                "control.linked_to_line",
                "control.unlinked_from_line",
                "incident.opened",
                "incident.closed",
                "policy.published",
                "vendor.created"
              ]
            },
            "minItems": 1,
            "maxItems": 50,
            "example": [
              "posture.changed",
              "risk.created"
            ]
          }
        },
        "required": [
          "url",
          "events"
        ]
      },
      "FairExposureSummaryDto": {
        "type": "object",
        "properties": {
          "totalQuantifiedRisks": {
            "type": "number",
            "example": 6,
            "description": "Number of risks with a FAIR quantification on file"
          },
          "totalAle": {
            "type": "number",
            "example": 742300,
            "description": "Sum of each quantified risk's median (P50) ALE"
          },
          "totalAleLow": {
            "type": "number",
            "example": 410900,
            "description": "Sum of each quantified risk's P10 (low) ALE"
          },
          "totalAleHigh": {
            "type": "number",
            "example": 1205600,
            "description": "Sum of each quantified risk's P90 (high) ALE"
          },
          "risks": {
            "description": "Per-risk breakdown, sorted by ALE descending",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FairRiskExposureDto"
            }
          }
        },
        "required": [
          "totalQuantifiedRisks",
          "totalAle",
          "totalAleLow",
          "totalAleHigh",
          "risks"
        ]
      },
      "FairLossExposureDto": {
        "type": "object",
        "properties": {
          "available": {
            "type": "boolean",
            "example": true,
            "description": "False when this risk has never been FAIR-quantified — the ale/aleLow/aleHigh fields are then null rather than fabricated"
          },
          "riskId": {
            "type": "string",
            "example": "RSK-001",
            "description": "Human-readable risk ID"
          },
          "ale": {
            "type": "object",
            "example": 182500.5,
            "description": "Median (P50) annualized loss expectancy in USD",
            "nullable": true
          },
          "aleLow": {
            "type": "object",
            "example": 94200,
            "description": "10th percentile (P10) annualized loss in USD",
            "nullable": true
          },
          "aleHigh": {
            "type": "object",
            "example": 310750,
            "description": "90th percentile (P90) annualized loss in USD",
            "nullable": true
          },
          "confidenceLevel": {
            "type": "object",
            "example": 0.3,
            "description": "Variance used to derive the simulated distribution",
            "nullable": true
          },
          "computedAt": {
            "type": "object",
            "example": "2026-06-30T00:00:00.000Z",
            "description": "When this assessment was computed",
            "nullable": true
          }
        },
        "required": [
          "available",
          "riskId"
        ]
      },
      "FairRiskExposureDto": {
        "type": "object",
        "properties": {
          "riskId": {
            "type": "string",
            "example": "RSK-001",
            "description": "Human-readable risk ID"
          },
          "riskTitle": {
            "type": "string",
            "example": "Unpatched critical vulnerabilities in production"
          },
          "ale": {
            "type": "object",
            "example": 182500.5,
            "description": "Median (P50) annualized loss expectancy in USD",
            "nullable": true
          },
          "aleLow": {
            "type": "object",
            "example": 94200,
            "description": "10th percentile (P10) annualized loss in USD",
            "nullable": true
          },
          "aleHigh": {
            "type": "object",
            "example": 310750,
            "description": "90th percentile (P90) annualized loss in USD",
            "nullable": true
          },
          "lastComputedAt": {
            "type": "object",
            "example": "2026-06-30T00:00:00.000Z",
            "description": "When this risk was last quantified",
            "nullable": true
          }
        },
        "required": [
          "riskId",
          "riskTitle"
        ]
      },
      "FrameworkControlsResponseDto": {
        "type": "object",
        "properties": {
          "framework": {
            "type": "object",
            "description": "Framework metadata"
          },
          "controls": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ControlStatusDto"
            }
          }
        },
        "required": [
          "framework",
          "controls"
        ]
      },
      "GateEvaluateBodyDto": {
        "type": "object",
        "properties": {
          "scoreDropThreshold": {
            "type": "number",
            "minimum": 0,
            "maximum": 100,
            "example": 5,
            "description": "Fail if the rollup score dropped more than this since the previous snapshot."
          },
          "blockOnControlRegression": {
            "type": "boolean",
            "example": true
          },
          "controlRegressionWindowDays": {
            "type": "integer",
            "minimum": 1,
            "maximum": 3650,
            "example": 30,
            "description": "Lookback window for the regression check."
          },
          "blockOnExpiredException": {
            "type": "boolean",
            "example": true
          },
          "modelSlug": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100,
            "example": "cmmc-l2",
            "description": "Evaluate against this maturity model instead of the tenant default."
          }
        }
      },
      "GateEvaluationResultDto": {
        "type": "object",
        "properties": {
          "pass": {
            "type": "boolean",
            "example": false,
            "description": "True when no check cited a failure. This is `pass`, not `passed`."
          },
          "evaluatedAt": {
            "type": "string",
            "example": "2026-10-09T12:00:00.000Z"
          },
          "policy": {
            "description": "The effective policy: the saved one with this request's overrides applied.",
            "allOf": [
              {
                "$ref": "#/components/schemas/GatePolicyDto"
              }
            ]
          },
          "summary": {
            "$ref": "#/components/schemas/GateSummaryDto"
          },
          "failures": {
            "description": "One cited reason per failure; empty when `pass` is true.",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/GateFailureDto"
            }
          }
        },
        "required": [
          "pass",
          "evaluatedAt",
          "policy",
          "summary",
          "failures"
        ]
      },
      "GateFailureDto": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "SCORE_DROP",
              "CONTROL_REGRESSION",
              "EXPIRED_EXCEPTION"
            ],
            "example": "CONTROL_REGRESSION"
          },
          "reason": {
            "type": "string",
            "example": "Control PR.AC-1 (nist-csf) moved from IMPLEMENTED to IN_PROGRESS on 2026-10-08"
          },
          "modelSlug": {
            "type": "string",
            "example": "nist-csf-2-tiers",
            "description": "Score-drop failures: the maturity model."
          },
          "modelName": {
            "type": "string",
            "description": "Score-drop failures: the model name."
          },
          "latestScore": {
            "type": "number",
            "description": "Score-drop failures: the latest rollup score."
          },
          "baselineScore": {
            "type": "number",
            "description": "Score-drop failures: the score it was compared against."
          },
          "drop": {
            "type": "number",
            "description": "Score-drop failures: baselineScore minus latestScore."
          },
          "threshold": {
            "type": "number",
            "description": "Score-drop failures: the threshold that was exceeded."
          },
          "latestComputedAt": {
            "type": "string",
            "description": "Score-drop failures: when the latest snapshot was computed."
          },
          "baselineComputedAt": {
            "type": "string",
            "description": "Score-drop failures: when the baseline snapshot was computed."
          },
          "controlAssessmentId": {
            "type": "string",
            "description": "Control-regression failures: the assessment row id."
          },
          "controlId": {
            "type": "string",
            "example": "PR.AC-1",
            "description": "Control-regression failures: the control key."
          },
          "frameworkSlug": {
            "type": "string",
            "example": "nist-csf",
            "description": "Control-regression failures: the framework slug."
          },
          "fromStatus": {
            "type": "string",
            "example": "IMPLEMENTED",
            "description": "Control-regression failures: the earlier status."
          },
          "toStatus": {
            "type": "string",
            "example": "IN_PROGRESS",
            "description": "Control-regression failures: the later status."
          },
          "changedAt": {
            "type": "string",
            "description": "Control-regression failures: when the status moved."
          },
          "exceptionId": {
            "type": "string",
            "description": "Expired-exception failures: the exception id."
          },
          "title": {
            "type": "string",
            "description": "Expired-exception failures: the exception title."
          },
          "targetType": {
            "type": "string",
            "description": "Expired-exception failures: what the exception covers (e.g. CONTROL)."
          },
          "targetRef": {
            "type": "string",
            "description": "Expired-exception failures: the covered target reference."
          },
          "expiresAt": {
            "type": "string",
            "description": "Expired-exception failures: when the exception expired."
          }
        },
        "required": [
          "type",
          "reason"
        ]
      },
      "GatePolicyDto": {
        "type": "object",
        "properties": {
          "scoreDropThreshold": {
            "type": "number",
            "example": 5
          },
          "blockOnControlRegression": {
            "type": "boolean",
            "example": true
          },
          "controlRegressionWindowDays": {
            "type": "number",
            "example": 7
          },
          "blockOnExpiredException": {
            "type": "boolean",
            "example": true
          },
          "updatedAt": {
            "type": "string",
            "nullable": true,
            "example": null,
            "description": "`null` when the policy was never saved (defaults apply)."
          }
        },
        "required": [
          "scoreDropThreshold",
          "blockOnControlRegression",
          "controlRegressionWindowDays",
          "blockOnExpiredException",
          "updatedAt"
        ]
      },
      "GateSummaryDto": {
        "type": "object",
        "properties": {
          "modelsChecked": {
            "example": [
              "nist-csf-2-tiers"
            ],
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "controlRegressionsFound": {
            "type": "number",
            "example": 1
          },
          "expiredExceptionsFound": {
            "type": "number",
            "example": 0
          }
        },
        "required": [
          "modelsChecked",
          "controlRegressionsFound",
          "expiredExceptionsFound"
        ]
      },
      "GenerateReportBodyDto": {
        "type": "object",
        "properties": {
          "templateId": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100,
            "example": "tmpl-uuid-001",
            "description": "Generate from a saved template; overrides below still apply on top."
          },
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200,
            "example": "Q3 Board Pack"
          },
          "sections": {
            "type": "array",
            "items": {
              "type": "string",
              "minLength": 1,
              "maxLength": 100
            },
            "maxItems": 50,
            "example": [
              "posture",
              "compliance",
              "risks"
            ],
            "description": "Ad-hoc section list — ignored if templateId is given and this is omitted."
          },
          "format": {
            "type": "string",
            "enum": [
              "PDF",
              "PPTX"
            ],
            "example": "PDF"
          },
          "includeRoadmap": {
            "type": "boolean",
            "example": true
          },
          "dateFrom": {
            "type": "string",
            "maxLength": 50,
            "example": "2026-06-01"
          },
          "dateTo": {
            "type": "string",
            "maxLength": 50,
            "example": "2026-08-31"
          },
          "brandAs": {
            "type": "string",
            "enum": [
              "self",
              "parent"
            ],
            "example": "self",
            "description": "MSP/vCISO reports only — brand as your own org or the parent."
          }
        }
      },
      "GeneratedReportSummaryDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "report-uuid-001"
          },
          "templateId": {
            "type": "object",
            "example": "tmpl-uuid-001",
            "nullable": true
          },
          "name": {
            "type": "string",
            "example": "Q3 Board Pack"
          },
          "sections": {
            "example": [
              "posture",
              "compliance",
              "risks"
            ],
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "format": {
            "type": "string",
            "example": "PDF",
            "enum": [
              "PDF",
              "PPTX"
            ]
          },
          "status": {
            "type": "string",
            "example": "COMPLETE",
            "enum": [
              "PENDING",
              "COMPLETE",
              "FAILED"
            ]
          },
          "downloadUrl": {
            "type": "object",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "example": "2026-08-24T12:00:00.000Z"
          }
        },
        "required": [
          "id",
          "name",
          "sections",
          "format",
          "status",
          "createdAt"
        ]
      },
      "IntegrationConnectionDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "conn-uuid-001"
          },
          "provider": {
            "type": "string",
            "example": "MICROSOFT_DEFENDER"
          },
          "status": {
            "type": "string",
            "example": "ACTIVE",
            "enum": [
              "PENDING",
              "ACTIVE",
              "UNHEALTHY",
              "DISCONNECTED",
              "DEACTIVATED"
            ]
          },
          "lastSyncAt": {
            "type": "object",
            "example": "2026-03-31T11:30:00.000Z",
            "nullable": true
          },
          "lastSyncStatus": {
            "type": "object",
            "example": "SUCCESS",
            "nullable": true
          },
          "syncInterval": {
            "type": "number",
            "example": 3600,
            "description": "The configured sync interval in seconds, as stored"
          },
          "effectiveSyncInterval": {
            "type": "number",
            "example": 86400,
            "description": "Seconds between scheduled syncs after the plan floor is applied (Free 24 h, Starter 4 h, Essentials and above 1 h, where the deployment enforces it) — the cadence the connection actually runs at"
          },
          "createdAt": {
            "type": "string",
            "example": "2026-01-15T08:00:00.000Z"
          }
        },
        "required": [
          "id",
          "provider",
          "status",
          "syncInterval",
          "effectiveSyncInterval",
          "createdAt"
        ]
      },
      "IntegrationHealthDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "conn-uuid-001"
          },
          "provider": {
            "type": "string",
            "example": "MICROSOFT_DEFENDER"
          },
          "status": {
            "type": "string",
            "example": "ACTIVE"
          },
          "lastSyncAt": {
            "type": "object",
            "nullable": true
          },
          "lastSyncStatus": {
            "type": "object",
            "nullable": true
          },
          "recentSyncLogs": {
            "description": "Last 5 sync logs",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SyncLogDto"
            }
          }
        },
        "required": [
          "id",
          "provider",
          "status",
          "recentSyncLogs"
        ]
      },
      "IntegrationSyncResultDto": {
        "type": "object",
        "properties": {
          "enqueued": {
            "type": "boolean",
            "example": true
          },
          "connectionId": {
            "type": "string",
            "example": "conn-uuid-001"
          }
        },
        "required": [
          "enqueued",
          "connectionId"
        ]
      },
      "PartnerClientDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "3f9a1c52-0000-4000-8000-000000000003",
            "description": "The client workspace id."
          },
          "name": {
            "type": "string",
            "example": "Acme Dental Group"
          }
        },
        "required": [
          "id",
          "name"
        ]
      },
      "PartnerClientsPageDto": {
        "type": "object",
        "properties": {
          "clients": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PartnerClientDto"
            }
          },
          "nextCursor": {
            "type": "string",
            "nullable": true,
            "example": null,
            "description": "Pass as `after` to read the next page; `null` when this is the last page."
          }
        },
        "required": [
          "clients",
          "nextCursor"
        ]
      },
      "PartnerControlDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "9d2b7c11-0000-4000-8000-000000000010",
            "description": "Control row id."
          },
          "controlId": {
            "type": "string",
            "example": "PR.AC-1"
          },
          "title": {
            "type": "string",
            "example": "Identities and credentials are managed"
          },
          "framework": {
            "type": "string",
            "example": "nist-csf",
            "description": "Framework slug."
          },
          "status": {
            "type": "string",
            "example": "IMPLEMENTED",
            "enum": [
              "NOT_STARTED",
              "IN_PROGRESS",
              "IMPLEMENTED",
              "AT_RISK",
              "FAILING",
              "NOT_APPLICABLE"
            ],
            "description": "Effective status (stale evidence downgrades it)."
          },
          "degraded": {
            "type": "boolean",
            "example": false,
            "description": "True when the stored status was downgraded for stale evidence."
          },
          "lastEvaluatedAt": {
            "type": "string",
            "nullable": true,
            "example": "2026-10-08T10:00:00.000Z"
          }
        },
        "required": [
          "id",
          "controlId",
          "title",
          "framework",
          "status",
          "degraded",
          "lastEvaluatedAt"
        ]
      },
      "PartnerControlsDto": {
        "type": "object",
        "properties": {
          "client": {
            "$ref": "#/components/schemas/PartnerClientDto"
          },
          "framework": {
            "type": "string",
            "nullable": true,
            "example": "nist-csf",
            "description": "The `framework` filter, or `null`."
          },
          "truncated": {
            "type": "boolean",
            "example": false,
            "description": "True when the list was cut at the server-side cap."
          },
          "controls": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PartnerControlDto"
            }
          }
        },
        "required": [
          "client",
          "framework",
          "truncated",
          "controls"
        ]
      },
      "PartnerPostureDto": {
        "type": "object",
        "properties": {
          "client": {
            "$ref": "#/components/schemas/PartnerClientDto"
          },
          "posture": {
            "nullable": true,
            "description": "The latest posture snapshot, or `null` when the client has none yet.",
            "type": "object",
            "allOf": [
              {
                "$ref": "#/components/schemas/PostureLatestDto"
              }
            ]
          }
        },
        "required": [
          "client",
          "posture"
        ]
      },
      "PartnerReceiptDto": {
        "type": "object",
        "properties": {
          "receiptId": {
            "type": "string",
            "example": "rcpt_0123456789abcdef"
          },
          "receiptDigest": {
            "type": "string",
            "example": "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
          },
          "claimId": {
            "type": "string",
            "example": "mfa-enforced"
          },
          "status": {
            "type": "string",
            "example": "ACTIVE",
            "enum": [
              "ACTIVE",
              "SUPERSEDED",
              "REVOKED"
            ]
          },
          "assertionKind": {
            "type": "string",
            "example": "ALL",
            "enum": [
              "ALL",
              "AT_LEAST",
              "RATIO",
              "SCORE",
              "SELF_ATTESTED"
            ]
          },
          "issuedAt": {
            "type": "string",
            "example": "2026-10-08T10:00:00.000Z"
          },
          "freshUntil": {
            "type": "string",
            "example": "2026-11-07T10:00:00.000Z"
          },
          "verifyCommand": {
            "type": "string",
            "example": "cmdash-verify cmdash-receipt-rcpt_0123456789abcdef.json --fetch --deployment https://app.cmdash.example",
            "description": "The command that verifies this receipt offline against the issuing deployment."
          }
        },
        "required": [
          "receiptId",
          "receiptDigest",
          "claimId",
          "status",
          "assertionKind",
          "issuedAt",
          "freshUntil",
          "verifyCommand"
        ]
      },
      "PartnerReceiptsDto": {
        "type": "object",
        "properties": {
          "client": {
            "$ref": "#/components/schemas/PartnerClientDto"
          },
          "truncated": {
            "type": "boolean",
            "example": false
          },
          "receipts": {
            "description": "Ids, digest, status and times only — never the receipt payload.",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PartnerReceiptDto"
            }
          }
        },
        "required": [
          "client",
          "truncated",
          "receipts"
        ]
      },
      "PartnerRemediationDto": {
        "type": "object",
        "properties": {
          "client": {
            "$ref": "#/components/schemas/PartnerClientDto"
          },
          "truncated": {
            "type": "boolean",
            "example": false
          },
          "tasks": {
            "description": "Open (not CLOSED) tasks, earliest due date first.",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PartnerRemediationTaskDto"
            }
          }
        },
        "required": [
          "client",
          "truncated",
          "tasks"
        ]
      },
      "PartnerRemediationTaskDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "b7a1d3f0-0000-4000-8000-000000000020"
          },
          "title": {
            "type": "string",
            "example": "Enforce MFA for all administrators"
          },
          "priority": {
            "type": "string",
            "example": "HIGH",
            "enum": [
              "LOW",
              "MEDIUM",
              "HIGH",
              "CRITICAL"
            ]
          },
          "status": {
            "type": "string",
            "example": "IN_PROGRESS",
            "enum": [
              "OPEN",
              "IN_PROGRESS",
              "VERIFICATION"
            ],
            "description": "Open tasks only (CLOSED is excluded)."
          },
          "dueDate": {
            "type": "string",
            "nullable": true,
            "example": "2026-11-01T00:00:00.000Z"
          },
          "overdue": {
            "type": "boolean",
            "example": false
          },
          "createdAt": {
            "type": "string",
            "example": "2026-10-01T09:30:00.000Z"
          }
        },
        "required": [
          "id",
          "title",
          "priority",
          "status",
          "dueDate",
          "overdue",
          "createdAt"
        ]
      },
      "PostureHistoryPointDto": {
        "type": "object",
        "properties": {
          "score": {
            "type": "number",
            "example": 74
          },
          "grade": {
            "type": "string",
            "example": "C"
          },
          "delta": {
            "type": "object",
            "example": 3,
            "nullable": true
          },
          "computedAt": {
            "type": "string",
            "example": "2026-03-31T12:00:00.000Z"
          }
        },
        "required": [
          "score",
          "grade",
          "delta",
          "computedAt"
        ]
      },
      "PostureLatestDto": {
        "type": "object",
        "properties": {
          "score": {
            "type": "number",
            "example": 74,
            "description": "Overall posture score (0–100)"
          },
          "grade": {
            "type": "string",
            "example": "C",
            "description": "Letter grade (A–F)",
            "enum": [
              "A",
              "B",
              "C",
              "D",
              "F"
            ]
          },
          "delta": {
            "type": "object",
            "example": 3,
            "description": "Score delta since last computation",
            "nullable": true
          },
          "computedAt": {
            "type": "string",
            "example": "2026-03-31T12:00:00.000Z",
            "description": "ISO 8601 timestamp"
          },
          "signals": {
            "description": "Individual signal breakdown",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PostureSignalDto"
            }
          }
        },
        "required": [
          "score",
          "grade",
          "delta",
          "computedAt",
          "signals"
        ]
      },
      "PostureSignalBreakdownDto": {
        "type": "object",
        "properties": {
          "score": {
            "type": "number",
            "example": 74
          },
          "grade": {
            "type": "string",
            "example": "C"
          },
          "computedAt": {
            "type": "string",
            "example": "2026-03-31T12:00:00.000Z"
          },
          "signals": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PostureSignalWithContributionDto"
            }
          }
        },
        "required": [
          "score",
          "grade",
          "computedAt",
          "signals"
        ]
      },
      "PostureSignalDto": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "example": "MEAN_TIME_TO_PATCH",
            "description": "Signal type identifier"
          },
          "rawValue": {
            "type": "number",
            "example": 14.2,
            "description": "Raw signal value from integration data"
          },
          "normalized": {
            "type": "number",
            "example": 0.72,
            "description": "Normalized value (0–1)"
          },
          "weight": {
            "type": "number",
            "example": 0.25,
            "description": "Signal weight in overall score"
          }
        },
        "required": [
          "type",
          "rawValue",
          "normalized",
          "weight"
        ]
      },
      "PostureSignalWithContributionDto": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "example": "MEAN_TIME_TO_PATCH",
            "description": "Signal type identifier"
          },
          "rawValue": {
            "type": "number",
            "example": 14.2,
            "description": "Raw signal value from integration data"
          },
          "normalized": {
            "type": "number",
            "example": 0.72,
            "description": "Normalized value (0–1)"
          },
          "weight": {
            "type": "number",
            "example": 0.25,
            "description": "Signal weight in overall score"
          },
          "contribution": {
            "type": "number",
            "example": 0.18,
            "description": "Weighted contribution = normalized × weight"
          }
        },
        "required": [
          "type",
          "rawValue",
          "normalized",
          "weight",
          "contribution"
        ]
      },
      "PublicAssetDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "3f9a1c52-0000-4000-8000-000000000001"
          },
          "name": {
            "type": "string",
            "example": "Production database"
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "assetType": {
            "type": "string",
            "example": "SYSTEM",
            "enum": [
              "SYSTEM",
              "DATA_CLASS",
              "PROCESS"
            ]
          },
          "criticality": {
            "type": "string",
            "example": "HIGH",
            "enum": [
              "CRITICAL",
              "HIGH",
              "MEDIUM",
              "LOW"
            ]
          },
          "ownerId": {
            "type": "string",
            "nullable": true
          },
          "declaredRtoMinutes": {
            "type": "number",
            "nullable": true,
            "description": "Declared recovery time objective, minutes."
          },
          "declaredRpoMinutes": {
            "type": "number",
            "nullable": true,
            "description": "Declared recovery point objective, minutes."
          },
          "createdAt": {
            "type": "string",
            "example": "2026-10-01T12:00:00.000Z"
          },
          "updatedAt": {
            "type": "string",
            "example": "2026-10-01T12:00:00.000Z"
          }
        },
        "required": [
          "id",
          "name",
          "assetType",
          "criticality",
          "createdAt",
          "updatedAt"
        ]
      },
      "PublicAssetListDto": {
        "type": "object",
        "properties": {
          "total": {
            "type": "number",
            "example": 42,
            "description": "Total rows matching the filters (not just this page)."
          },
          "limit": {
            "type": "number",
            "example": 50,
            "description": "Page size applied (default 50, maximum 500)."
          },
          "offset": {
            "type": "number",
            "example": 0,
            "description": "Rows skipped."
          },
          "assets": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PublicAssetDto"
            }
          }
        },
        "required": [
          "total",
          "limit",
          "offset",
          "assets"
        ]
      },
      "PublicEvidenceDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "3f9a1c52-0000-4000-8000-000000000001"
          },
          "frameworkSlug": {
            "type": "string",
            "example": "soc2"
          },
          "controlId": {
            "type": "string",
            "example": "CC6.1"
          },
          "title": {
            "type": "string",
            "example": "Quarterly access review export"
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "sourceUrl": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "string",
            "example": "COLLECTED",
            "enum": [
              "PENDING",
              "COLLECTED",
              "REVIEWED",
              "APPROVED",
              "REJECTED"
            ]
          },
          "collectionMethod": {
            "type": "string",
            "example": "INTEGRATION",
            "enum": [
              "MANUAL",
              "AUTOMATED",
              "INTEGRATION"
            ]
          },
          "evidenceKind": {
            "type": "string",
            "example": "SIGNAL_SNAPSHOT"
          },
          "provenance": {
            "type": "string",
            "example": "MACHINE_COLLECTED",
            "enum": [
              "MACHINE_COLLECTED",
              "HUMAN_ATTESTED",
              "AI_DRAFTED",
              "UNKNOWN"
            ]
          },
          "collectedAt": {
            "type": "string",
            "example": "2026-10-01T12:00:00.000Z"
          },
          "reviewedAt": {
            "type": "string",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "example": "2026-10-01T12:00:00.000Z"
          },
          "updatedAt": {
            "type": "string",
            "example": "2026-10-01T12:00:00.000Z"
          }
        },
        "required": [
          "id",
          "frameworkSlug",
          "controlId",
          "title",
          "status",
          "collectionMethod",
          "evidenceKind",
          "provenance",
          "collectedAt",
          "createdAt",
          "updatedAt"
        ]
      },
      "PublicEvidenceListDto": {
        "type": "object",
        "properties": {
          "total": {
            "type": "number",
            "example": 42,
            "description": "Total rows matching the filters (not just this page)."
          },
          "limit": {
            "type": "number",
            "example": 50,
            "description": "Page size applied (default 50, maximum 500)."
          },
          "offset": {
            "type": "number",
            "example": 0,
            "description": "Rows skipped."
          },
          "evidence": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PublicEvidenceDto"
            }
          }
        },
        "required": [
          "total",
          "limit",
          "offset",
          "evidence"
        ]
      },
      "PublicIncidentControlLinkDto": {
        "type": "object",
        "properties": {
          "frameworkSlug": {
            "type": "string",
            "example": "soc2"
          },
          "controlId": {
            "type": "string",
            "example": "CC7.3"
          }
        },
        "required": [
          "frameworkSlug",
          "controlId"
        ]
      },
      "PublicIncidentDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "3f9a1c52-0000-4000-8000-000000000001"
          },
          "title": {
            "type": "string",
            "example": "Credential stuffing against SSO"
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "severity": {
            "type": "string",
            "example": "HIGH",
            "enum": [
              "CRITICAL",
              "HIGH",
              "MEDIUM",
              "LOW"
            ]
          },
          "status": {
            "type": "string",
            "example": "OPEN",
            "enum": [
              "OPEN",
              "CONTAINED",
              "MONITORING",
              "CLOSED"
            ]
          },
          "openedAt": {
            "type": "string",
            "example": "2026-10-01T12:00:00.000Z"
          },
          "closedAt": {
            "type": "string",
            "nullable": true
          },
          "controls": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PublicIncidentControlLinkDto"
            }
          },
          "createdAt": {
            "type": "string",
            "example": "2026-10-01T12:00:00.000Z"
          },
          "updatedAt": {
            "type": "string",
            "example": "2026-10-01T12:00:00.000Z"
          }
        },
        "required": [
          "id",
          "title",
          "severity",
          "status",
          "openedAt",
          "controls",
          "createdAt",
          "updatedAt"
        ]
      },
      "PublicIncidentListDto": {
        "type": "object",
        "properties": {
          "total": {
            "type": "number",
            "example": 42,
            "description": "Total rows matching the filters (not just this page)."
          },
          "limit": {
            "type": "number",
            "example": 50,
            "description": "Page size applied (default 50, maximum 500)."
          },
          "offset": {
            "type": "number",
            "example": 0,
            "description": "Rows skipped."
          },
          "incidents": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PublicIncidentDto"
            }
          }
        },
        "required": [
          "total",
          "limit",
          "offset",
          "incidents"
        ]
      },
      "PublicPolicyDetailDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "3f9a1c52-0000-4000-8000-000000000001"
          },
          "slug": {
            "type": "string",
            "example": "acceptable-use"
          },
          "title": {
            "type": "string",
            "example": "Acceptable Use Policy"
          },
          "category": {
            "type": "string",
            "example": "SECURITY",
            "enum": [
              "SECURITY",
              "PRIVACY",
              "OPERATIONAL",
              "HR",
              "INCIDENT_RESPONSE",
              "BCDR",
              "COMPLIANCE",
              "GENERAL"
            ]
          },
          "status": {
            "type": "string",
            "example": "ACTIVE",
            "enum": [
              "DRAFT",
              "IN_REVIEW",
              "ACTIVE",
              "RETIRED"
            ]
          },
          "tags": {
            "example": [
              "soc2"
            ],
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "reviewCadenceDays": {
            "type": "number",
            "example": 365
          },
          "ownerId": {
            "type": "string",
            "nullable": true
          },
          "activeVersionNumber": {
            "type": "number",
            "nullable": true,
            "example": 3,
            "description": "Version number of the active (published) version, or null for a policy never published."
          },
          "createdAt": {
            "type": "string",
            "example": "2026-10-01T12:00:00.000Z"
          },
          "updatedAt": {
            "type": "string",
            "example": "2026-10-01T12:00:00.000Z"
          },
          "activeContent": {
            "type": "string",
            "nullable": true,
            "description": "Markdown source of the active version; null when none is published."
          },
          "versions": {
            "description": "Published versions, newest first (at most 100).",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PublicPolicyVersionDto"
            }
          }
        },
        "required": [
          "id",
          "slug",
          "title",
          "category",
          "status",
          "tags",
          "reviewCadenceDays",
          "createdAt",
          "updatedAt",
          "versions"
        ]
      },
      "PublicPolicyDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "3f9a1c52-0000-4000-8000-000000000001"
          },
          "slug": {
            "type": "string",
            "example": "acceptable-use"
          },
          "title": {
            "type": "string",
            "example": "Acceptable Use Policy"
          },
          "category": {
            "type": "string",
            "example": "SECURITY",
            "enum": [
              "SECURITY",
              "PRIVACY",
              "OPERATIONAL",
              "HR",
              "INCIDENT_RESPONSE",
              "BCDR",
              "COMPLIANCE",
              "GENERAL"
            ]
          },
          "status": {
            "type": "string",
            "example": "ACTIVE",
            "enum": [
              "DRAFT",
              "IN_REVIEW",
              "ACTIVE",
              "RETIRED"
            ]
          },
          "tags": {
            "example": [
              "soc2"
            ],
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "reviewCadenceDays": {
            "type": "number",
            "example": 365
          },
          "ownerId": {
            "type": "string",
            "nullable": true
          },
          "activeVersionNumber": {
            "type": "number",
            "nullable": true,
            "example": 3,
            "description": "Version number of the active (published) version, or null for a policy never published."
          },
          "createdAt": {
            "type": "string",
            "example": "2026-10-01T12:00:00.000Z"
          },
          "updatedAt": {
            "type": "string",
            "example": "2026-10-01T12:00:00.000Z"
          }
        },
        "required": [
          "id",
          "slug",
          "title",
          "category",
          "status",
          "tags",
          "reviewCadenceDays",
          "createdAt",
          "updatedAt"
        ]
      },
      "PublicPolicyListDto": {
        "type": "object",
        "properties": {
          "total": {
            "type": "number",
            "example": 42,
            "description": "Total rows matching the filters (not just this page)."
          },
          "limit": {
            "type": "number",
            "example": 50,
            "description": "Page size applied (default 50, maximum 500)."
          },
          "offset": {
            "type": "number",
            "example": 0,
            "description": "Rows skipped."
          },
          "policies": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PublicPolicyDto"
            }
          }
        },
        "required": [
          "total",
          "limit",
          "offset",
          "policies"
        ]
      },
      "PublicPolicyVersionDto": {
        "type": "object",
        "properties": {
          "versionNumber": {
            "type": "number",
            "example": 3
          },
          "changeSummary": {
            "type": "string",
            "nullable": true
          },
          "publishedAt": {
            "type": "string",
            "example": "2026-10-01T12:00:00.000Z"
          }
        },
        "required": [
          "versionNumber",
          "publishedAt"
        ]
      },
      "PublicVendorDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "3f9a1c52-0000-4000-8000-000000000001"
          },
          "name": {
            "type": "string",
            "example": "Acme Hosting"
          },
          "website": {
            "type": "string",
            "nullable": true
          },
          "industry": {
            "type": "string",
            "nullable": true
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "criticality": {
            "type": "string",
            "example": "HIGH",
            "enum": [
              "CRITICAL",
              "HIGH",
              "MODERATE",
              "LOW"
            ]
          },
          "dataSensitivity": {
            "type": "string",
            "example": "PII",
            "enum": [
              "CUI",
              "PHI",
              "PII",
              "CONFIDENTIAL",
              "INTERNAL",
              "PUBLIC",
              "NONE"
            ]
          },
          "tier": {
            "type": "string",
            "example": "T2",
            "enum": [
              "T1",
              "T2",
              "T3",
              "T4"
            ]
          },
          "status": {
            "type": "string",
            "example": "ACTIVE",
            "enum": [
              "ONBOARDING",
              "ACTIVE",
              "UNDER_REVIEW",
              "OFFBOARDING",
              "RETIRED"
            ]
          },
          "startedAt": {
            "type": "string",
            "nullable": true
          },
          "endsAt": {
            "type": "string",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "example": "2026-10-01T12:00:00.000Z"
          },
          "updatedAt": {
            "type": "string",
            "example": "2026-10-01T12:00:00.000Z"
          }
        },
        "required": [
          "id",
          "name",
          "criticality",
          "dataSensitivity",
          "tier",
          "status",
          "createdAt",
          "updatedAt"
        ]
      },
      "PublicVendorListDto": {
        "type": "object",
        "properties": {
          "total": {
            "type": "number",
            "example": 42,
            "description": "Total rows matching the filters (not just this page)."
          },
          "limit": {
            "type": "number",
            "example": 50,
            "description": "Page size applied (default 50, maximum 500)."
          },
          "offset": {
            "type": "number",
            "example": 0,
            "description": "Rows skipped."
          },
          "vendors": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PublicVendorDto"
            }
          }
        },
        "required": [
          "total",
          "limit",
          "offset",
          "vendors"
        ]
      },
      "PushEvidenceDto": {
        "type": "object",
        "properties": {
          "items": {
            "description": "Up to 200 items.",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PushEvidenceItemDto"
            }
          }
        },
        "required": [
          "items"
        ]
      },
      "PushEvidenceItemDto": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string",
            "description": "Human-readable title for the artifact."
          },
          "description": {
            "type": "string"
          },
          "controlId": {
            "type": "string",
            "description": "Control identifier within the framework, e.g. \"CC6.1\"."
          },
          "frameworkSlug": {
            "type": "string",
            "description": "Framework slug, e.g. \"soc2\"."
          },
          "sourceUrl": {
            "type": "string",
            "description": "Link to the artifact in your own system."
          }
        },
        "required": [
          "title",
          "controlId",
          "frameworkSlug"
        ]
      },
      "PushEvidenceResultDto": {
        "type": "object",
        "properties": {
          "created": {
            "type": "number"
          },
          "ids": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        },
        "required": [
          "created",
          "ids"
        ]
      },
      "PushObservationItemDto": {
        "type": "object",
        "properties": {
          "externalId": {
            "type": "string",
            "description": "Stable identifier for this observation in YOUR system. Re-pushing the same value updates the existing row rather than creating another."
          },
          "observationType": {
            "type": "string",
            "enum": [
              "CSPM_CONTROL_STATE",
              "DETECTION_TECHNIQUE",
              "ATTACK_COVERAGE",
              "VULN_SLA_ADHERENCE",
              "IDENTITY_POSTURE",
              "BAS_CONTROL_VALIDATION",
              "SCAP_RULE_RESULT",
              "HUMAN_RISK",
              "RECOVERY_TEST_RESULT"
            ]
          },
          "metric": {
            "type": "object",
            "description": "Raw measurement object. Never a pre-computed 0-100 score."
          },
          "observedAt": {
            "type": "string",
            "description": "ISO-8601. Defaults to now."
          },
          "controlRef": {
            "type": "object",
            "description": "{ frameworkSlug, controlId } to stamp this against a control."
          },
          "sourceUrl": {
            "type": "string"
          }
        },
        "required": [
          "externalId",
          "observationType",
          "metric"
        ]
      },
      "PushObservationsDto": {
        "type": "object",
        "properties": {
          "items": {
            "description": "Up to 200 items.",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PushObservationItemDto"
            }
          }
        },
        "required": [
          "items"
        ]
      },
      "PushObservationsResultDto": {
        "type": "object",
        "properties": {
          "ingested": {
            "type": "number"
          }
        },
        "required": [
          "ingested"
        ]
      },
      "RateLimitErrorResponseDto": {
        "type": "object",
        "properties": {
          "success": {
            "type": "boolean",
            "example": false
          },
          "requestId": {
            "type": "string",
            "example": "550e8400-e29b-41d4-a716-446655440000"
          },
          "timestamp": {
            "type": "string",
            "example": "2026-03-31T12:00:00.000Z"
          },
          "statusCode": {
            "type": "number",
            "example": 429
          },
          "error": {
            "type": "string",
            "example": "Too Many Requests"
          },
          "message": {
            "type": "string",
            "example": "Rate limit exceeded. Try again in 42 seconds."
          }
        },
        "required": [
          "success",
          "requestId",
          "timestamp",
          "statusCode",
          "error",
          "message"
        ]
      },
      "ReportContentDto": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "example": "Q3 Board Pack"
          },
          "format": {
            "type": "string",
            "example": "PDF",
            "enum": [
              "PDF",
              "PPTX"
            ]
          },
          "status": {
            "type": "string",
            "example": "PROCESSING",
            "enum": [
              "PENDING",
              "PROCESSING",
              "COMPLETE",
              "FAILED"
            ],
            "description": "The job status. The same four values every async job in CMDash reports."
          },
          "ready": {
            "type": "boolean",
            "example": false,
            "description": "`content` is populated only when this is true."
          },
          "terminal": {
            "type": "boolean",
            "example": false,
            "description": "True once the status can no longer change (COMPLETE or FAILED) — stop polling."
          },
          "retryAfterSeconds": {
            "type": "object",
            "example": 5,
            "nullable": true,
            "description": "Advisory seconds to wait before polling again; null once `terminal`."
          },
          "content": {
            "type": "object",
            "nullable": true,
            "description": "The assembled report content (shape varies by section/format). Null until `ready` is true."
          }
        },
        "required": [
          "name",
          "format",
          "status",
          "ready",
          "terminal",
          "retryAfterSeconds",
          "content"
        ]
      },
      "RiskControlLinkDto": {
        "type": "object",
        "properties": {
          "controlId": {
            "type": "string",
            "example": "AC.L2-3.1.1"
          },
          "title": {
            "type": "string",
            "example": "Limit system access to authorized users"
          },
          "domain": {
            "type": "string",
            "example": "Access Control"
          }
        },
        "required": [
          "controlId",
          "title",
          "domain"
        ]
      },
      "RiskItemDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "risk-uuid-001"
          },
          "riskId": {
            "type": "string",
            "example": "RSK-001",
            "description": "Human-readable risk ID"
          },
          "title": {
            "type": "string",
            "example": "Unpatched critical vulnerabilities in production"
          },
          "description": {
            "type": "object",
            "example": "Multiple CVEs with CVSS > 9.0 remain unpatched",
            "nullable": true
          },
          "category": {
            "type": "object",
            "example": "Vulnerability Management",
            "nullable": true
          },
          "likelihood": {
            "type": "number",
            "example": 4,
            "description": "Likelihood rating (1–5)"
          },
          "impact": {
            "type": "number",
            "example": 5,
            "description": "Impact rating (1–5)"
          },
          "riskScore": {
            "type": "number",
            "example": 20,
            "description": "Risk score = likelihood × impact"
          },
          "status": {
            "type": "string",
            "example": "OPEN",
            "enum": [
              "OPEN",
              "MITIGATED",
              "ACCEPTED",
              "TRANSFERRED",
              "CLOSED"
            ]
          },
          "dueDate": {
            "type": "object",
            "example": "2026-06-30T00:00:00.000Z",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "example": "2026-03-01T10:00:00.000Z"
          },
          "updatedAt": {
            "type": "string",
            "example": "2026-03-15T14:30:00.000Z"
          },
          "controls": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RiskControlLinkDto"
            }
          }
        },
        "required": [
          "id",
          "riskId",
          "title",
          "likelihood",
          "impact",
          "riskScore",
          "status",
          "createdAt",
          "updatedAt",
          "controls"
        ]
      },
      "RiskListResponseDto": {
        "type": "object",
        "properties": {
          "total": {
            "type": "number",
            "example": 12,
            "description": "Total matching risks"
          },
          "risks": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RiskItemDto"
            }
          }
        },
        "required": [
          "total",
          "risks"
        ]
      },
      "SyncLogDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "log-uuid-001"
          },
          "status": {
            "type": "string",
            "example": "SUCCESS",
            "enum": [
              "SUCCESS",
              "FAILED",
              "RUNNING"
            ]
          },
          "recordsSynced": {
            "type": "object",
            "example": 142,
            "nullable": true
          },
          "durationMs": {
            "type": "object",
            "example": 3420,
            "description": "Duration in milliseconds",
            "nullable": true
          },
          "errorMessage": {
            "type": "object",
            "example": null,
            "nullable": true
          },
          "startedAt": {
            "type": "string",
            "example": "2026-03-31T11:30:00.000Z"
          },
          "completedAt": {
            "type": "object",
            "example": "2026-03-31T11:30:03.420Z",
            "nullable": true
          }
        },
        "required": [
          "id",
          "status",
          "startedAt"
        ]
      },
      "UpdateRiskBodyDto": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string",
            "minLength": 1,
            "maxLength": 300,
            "example": "Updated risk title"
          },
          "description": {
            "type": "string",
            "maxLength": 5000,
            "nullable": true,
            "example": "Updated description"
          },
          "category": {
            "type": "string",
            "maxLength": 200,
            "nullable": true,
            "example": "Identity & Access"
          },
          "likelihood": {
            "type": "integer",
            "minimum": 1,
            "maximum": 5,
            "example": 3,
            "description": "Likelihood on the 1–5 ordinal scale"
          },
          "impact": {
            "type": "integer",
            "minimum": 1,
            "maximum": 5,
            "example": 4,
            "description": "Impact on the 1–5 ordinal scale"
          },
          "status": {
            "type": "string",
            "enum": [
              "OPEN",
              "IN_PROGRESS",
              "MITIGATED",
              "ACCEPTED",
              "CLOSED"
            ],
            "example": "MITIGATED"
          },
          "dueDate": {
            "type": "string",
            "maxLength": 50,
            "nullable": true,
            "example": "2026-09-30"
          },
          "notes": {
            "type": "string",
            "maxLength": 5000,
            "nullable": true,
            "example": "Applied patches, residual risk remains"
          }
        }
      },
      "UpdateWebhookBodyDto": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "minLength": 1,
            "maxLength": 2048,
            "example": "https://example.com/webhooks/cmdash"
          },
          "events": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "approval.request.completed",
                "posture.changed",
                "risk.created",
                "risk.updated",
                "compliance.milestone",
                "integration.synced",
                "integration.failed",
                "alert.triggered",
                "evidence.propagated",
                "webhook.registration.circuit_opened",
                "evidence.linked",
                "evidence.unlinked",
                "control.created",
                "control.linked_to_line",
                "control.unlinked_from_line",
                "incident.opened",
                "incident.closed",
                "policy.published",
                "vendor.created"
              ]
            },
            "minItems": 1,
            "maxItems": 50,
            "example": [
              "posture.changed"
            ]
          },
          "isActive": {
            "type": "boolean",
            "example": false
          }
        }
      },
      "WebhookCreateResultDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "wh-uuid-001"
          },
          "url": {
            "type": "string",
            "example": "https://example.com/webhooks/cmdash"
          },
          "events": {
            "example": [
              "posture.changed",
              "risk.created"
            ],
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "isActive": {
            "type": "boolean",
            "example": true
          },
          "createdAt": {
            "type": "string",
            "example": "2026-01-15T08:00:00.000Z"
          },
          "secret": {
            "type": "string",
            "example": "whsec_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6",
            "description": "HMAC signing secret — shown in PLAINTEXT exactly once, at creation or rotation. Store it securely."
          }
        },
        "required": [
          "id",
          "url",
          "events",
          "isActive",
          "createdAt",
          "secret"
        ]
      },
      "WebhookDeleteResultDto": {
        "type": "object",
        "properties": {
          "deleted": {
            "type": "boolean",
            "example": true
          }
        },
        "required": [
          "deleted"
        ]
      },
      "WebhookDeliveryLogDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "del-uuid-001"
          },
          "event": {
            "type": "string",
            "example": "posture.changed"
          },
          "payload": {
            "type": "object",
            "example": {
              "score": 74,
              "grade": "C",
              "delta": 3
            }
          },
          "responseCode": {
            "type": "object",
            "example": 200,
            "nullable": true
          },
          "status": {
            "type": "string",
            "example": "delivered",
            "enum": [
              "pending",
              "retrying",
              "delivered",
              "failed",
              "dead_lettered",
              "discarded"
            ]
          },
          "sequence": {
            "type": "object",
            "example": 42,
            "nullable": true,
            "description": "Per-registration gapless sequence; null for pre-E32 rows."
          },
          "eventId": {
            "type": "object",
            "example": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d",
            "nullable": true,
            "description": "Stable idempotency id — identical across retries and replays."
          },
          "isReplay": {
            "type": "boolean",
            "example": false,
            "description": "True when this row is a re-send rather than a live event."
          },
          "nextAttemptAt": {
            "type": "object",
            "example": "2026-03-31T12:01:00.000Z",
            "nullable": true
          },
          "deadLetterReason": {
            "type": "string",
            "example": "retries_exhausted",
            "nullable": true,
            "enum": [
              "retries_exhausted",
              "permanent_4xx",
              "circuit_open"
            ]
          },
          "attempts": {
            "type": "number",
            "example": 1
          },
          "deliveredAt": {
            "type": "object",
            "example": "2026-03-31T12:00:01.000Z",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "example": "2026-03-31T12:00:00.000Z"
          }
        },
        "required": [
          "id",
          "event",
          "payload",
          "status",
          "isReplay",
          "attempts",
          "createdAt"
        ]
      },
      "WebhookListItemDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "wh-uuid-001"
          },
          "url": {
            "type": "string",
            "example": "https://example.com/webhooks/cmdash"
          },
          "events": {
            "example": [
              "posture.changed",
              "risk.created"
            ],
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "isActive": {
            "type": "boolean",
            "example": true
          },
          "createdAt": {
            "type": "string",
            "example": "2026-01-15T08:00:00.000Z"
          }
        },
        "required": [
          "id",
          "url",
          "events",
          "isActive",
          "createdAt"
        ]
      },
      "WebhookRotateSecretResultDto": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "wh-uuid-001"
          },
          "secret": {
            "type": "string",
            "example": "whsec_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6",
            "description": "New plaintext secret — shown once."
          },
          "secretRotatedAt": {
            "type": "string",
            "example": "2026-08-24T12:00:00.000Z"
          },
          "previousSecretExpiresAt": {
            "type": "string",
            "example": "2026-08-25T12:00:00.000Z",
            "description": "The OLD secret keeps signing deliveries until this time."
          },
          "overlapHours": {
            "type": "number",
            "example": 24
          }
        },
        "required": [
          "id",
          "secret",
          "secretRotatedAt",
          "previousSecretExpiresAt",
          "overlapHours"
        ]
      },
      "WebhookTestResultDto": {
        "type": "object",
        "properties": {
          "success": {
            "type": "boolean",
            "example": true,
            "description": "True when the receiver answered 2xx."
          },
          "status": {
            "type": "number",
            "example": 200,
            "description": "The receiver's HTTP status; absent when no response arrived."
          },
          "message": {
            "type": "string",
            "example": "Delivered"
          },
          "latencyMs": {
            "type": "number",
            "example": 142
          },
          "responseBody": {
            "type": "string",
            "nullable": true,
            "description": "The receiver's response body, truncated."
          }
        },
        "required": [
          "success",
          "message",
          "latencyMs"
        ]
      }
    }
  }
}
