{
  "openapi": "3.1.0",
  "info": {
    "title": "Rhindon AI Risk & Integrity Cloud — REST API",
    "version": "1.7.0",
    "description": "Authenticated REST surface for RAIC. Bearer auth accepts a Supabase JWT or a reveal-once `rai_*` API token. Writes require `X-Idempotency-Key`.\n\n## Cyber Domain (v1.1.0, Jun 23 2026)\nEvery registry surface (ai-systems, risks, controls, use-cases) and the new Maturity surface now accept `?domain=ai|cyber|all`. Default `all` is back-compat — AI-only tenants see no change. Tokens carrying only `raic:cyber:read` / `raic:cyber:write` are server-side intersected to `domain=cyber` regardless of the query string. Risk writes validate subject/domain integrity and return `400 domain_mismatch` if a cyber-domain risk points at an AI-domain subject (or vice versa).\n\n## HIPAA Module (v1.2.0, Jul 2 2026)\nRead-only endpoints under `/v1/hipaa/*` (BAAs, Breaches, Safeguards) are **gated** by the per-org OrgAdmin toggle `app_settings.hipaa_mode_enabled`. When the toggle is off the endpoints return `404 route_not_found` — indistinguishable from a genuinely-missing route — so a caller cannot probe HIPAA data for a tenant that has not opted in. Scope: `raic:hipaa:read` (or the broader `raic:registry:read`).\n\n## Governance Policies (v1.3.0, Jul 2026)\nCRUD endpoints under `/v1/policies` expose the `governance_policies` register — title, version, category, domain (`ai|cyber`), status, effective/review dates, and attestation cadence. Scopes reuse the canonical registry taxonomy: `raic:registry:read` / `raic:registry:write` / `raic:registry:delete`. Writes emit `policy.created` / `policy.updated` / `policy.deleted` webhook events.\n\n## AI Catalog Curator Surface (v1.4.0, Jul 2026)\nEndpoints under `/v1/ai-catalog/*` expose the shared global AI catalog and a submission queue for approved external curator projects (e.g. RAIC Catalog Curator). Reads are keyset-paginated on `(updated_at, id) DESC` and return `meta.next_cursor`. Writes never touch `ai_catalog_entries` directly — every submission (`intent: new|patch|retire`) lands in `ai_catalog_signal_candidates` and still requires SuperAdmin promotion inside RAIC before it reaches installed browser extensions. Scopes: `raic:catalog:read` / `raic:catalog:write`. All writes require `X-Idempotency-Key` (canonical `curator:<intent>:<hash>`).\n\n## Host, tier and rate limits (v1.5.0, Aug 2026)\nThe API is served from the application host at `https://app.rhindoncyber.com/api/public/v1`. The retired pre-migration API host is no longer routed. API tokens are gated to the **Enterprise** tier. Server-enforced per-token sliding windows: read 120/min, write 20/min, bulk 5/min, heavy 10/min, admin 5/min; exceeding a window returns `429` with `Retry-After`.\n\n## AI Data Governance (v1.6.0, Aug 2026)\nEnterprise-gated endpoints under `/v1/data/*` cover flow ingest, DLP decisions, policy CRUD/simulation, the encrypted payload vault, and the in-line enforcement point. `POST /v1/data/proxy` classifies the forwarded payload, evaluates the tenant policy set through the same PDP used by ingest, and answers in-line — `200` with an optional `transformed_content` for allow/mask decisions, `403` when the applied action blocks or quarantines. `POST /v1/data/flows/batch` accepts up to 100 collector flows per call and returns a per-row result array. Scopes: `raic:data:read` / `raic:data:write` / `raic:data:policies:manage` / `raic:vault:read` (JWT only).\n\n## Shadow AI Bulk Ingestion (v1.7.0, Aug 2026)\nA machine lane for approved curator projects. `POST /v1/shadow-ai/registry-assets:bulk` and `POST /v1/shadow-ai/signatures:bulk` accept up to 1000 rows per call, persist the raw batch, and answer `202 {job_id}`; per-row status and errors are read back from `GET /v1/shadow-ai/jobs/{jobId}`. Idempotency key format `curator:registry:<registry>:<batchSeq>`; `X-Dry-Run: 1` previews without writing. Signature patterns are compiled and rejected when they exceed the complexity ceiling or match over-broadly. `GET /v1/shadow-ai/ruleset` publishes Layer 1-2 signatures as a compact shared ruleset and Layer 3-4 as a hash-to-asset lookup so detector config stays flat as the asset count grows. Writes are metered on a separate 120/min machine limiter, leaving the interactive catalog budget untouched. Scopes: `raic:shadow-ai:read` / `raic:shadow-ai:write`.",
    "contact": {
      "name": "RhindonCyber Support",
      "url": "https://app.rhindoncyber.com/support"
    }
  },
  "servers": [
    {
      "url": "https://app.rhindoncyber.com/api/public"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "AI Systems"
    },
    {
      "name": "Risks"
    },
    {
      "name": "Controls"
    },
    {
      "name": "Use Cases"
    },
    {
      "name": "Discovery"
    },
    {
      "name": "Suppliers"
    },
    {
      "name": "Audit"
    },
    {
      "name": "Maturity"
    },
    {
      "name": "Phishing"
    },
    {
      "name": "AI Systems"
    },
    {
      "name": "Risks"
    },
    {
      "name": "Controls"
    },
    {
      "name": "Use Cases"
    },
    {
      "name": "Discovery"
    },
    {
      "name": "Suppliers"
    },
    {
      "name": "Audit"
    },
    {
      "name": "Maturity"
    },
    {
      "name": "Phishing"
    },
    {
      "name": "HIPAA"
    },
    {
      "name": "Policies"
    },
    {
      "name": "AI Catalog"
    },
    {
      "name": "Data Governance"
    },
    {
      "name": "Meta"
    },
    {
      "name": "Credentials"
    }
  ],
  "paths": {
    "/v1/openapi.json": {
      "get": {
        "tags": [
          "Meta"
        ],
        "summary": "OpenAPI 3.1 spec for this API",
        "security": [],
        "responses": {
          "200": {
            "description": "Spec"
          }
        }
      }
    },
    "/v1/credentials/health": {
      "get": {
        "tags": [
          "Credentials"
        ],
        "summary": "Pre-flight check the caller's bearer token (any scope; 200 = token is valid for the resolved org)",
        "responses": {
          "200": {
            "description": "Token resolved",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing/invalid bearer"
          },
          "403": {
            "description": "Token revoked or org suspended"
          }
        }
      }
    },
    "/v1/ai-systems": {
      "get": {
        "tags": [
          "AI Systems"
        ],
        "summary": "List AI systems (supports offset OR opaque cursor + updated_since + domain)",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Opaque base64url forward-pagination cursor. When set, `offset` is ignored and `meta.next_cursor` advances the page."
          },
          {
            "name": "updated_since",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "ISO-8601 UTC timestamp. Returns rows with `updated_at >= updated_since`."
          },
          {
            "name": "domain",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "ai",
                "cyber",
                "all"
              ],
              "default": "all"
            },
            "description": "Filter by registry domain. `ai` = AI Governance rows, `cyber` = Cybersecurity rows, `all` (default) = both. Tokens carrying only `raic:cyber:*` are forced to `cyber` regardless."
          }
        ],
        "responses": {
          "200": {
            "description": "OK — `meta.next_cursor` is non-null when more pages exist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_domain` when `?domain=` is set to an unsupported value."
          }
        }
      }
    },
    "/v1/ai-systems/{id}": {
      "get": {
        "tags": [
          "AI Systems"
        ],
        "summary": "Fetch one AI system by id or system_id",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    },
    "/v1/risks": {
      "get": {
        "tags": [
          "Risks"
        ],
        "summary": "List risks (supports offset OR opaque cursor + filters)",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Opaque base64url forward-pagination cursor. When set, `offset` is ignored and `meta.next_cursor` advances the page."
          },
          {
            "name": "updated_since",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "ISO-8601 UTC timestamp. Returns rows with `updated_at >= updated_since`."
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated status filter (e.g. `resolved,closed,mitigated`)."
          },
          {
            "name": "domain",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "ai",
                "cyber",
                "all"
              ],
              "default": "all"
            },
            "description": "Filter by registry domain. `ai` = AI Governance rows, `cyber` = Cybersecurity rows, `all` (default) = both. Tokens carrying only `raic:cyber:*` are forced to `cyber` regardless."
          }
        ],
        "responses": {
          "200": {
            "description": "OK — `meta.next_cursor` is non-null when more pages exist. `meta.domain` echoes the effective filter (post scope-intersection).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_domain` when `?domain=` is set to an unsupported value."
          }
        }
      },
      "post": {
        "tags": [
          "Risks"
        ],
        "summary": "Create a risk (accepts `domain`)",
        "parameters": [
          {
            "name": "X-Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200
            }
          },
          {
            "name": "X-Dry-Run",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ]
            },
            "description": "Set to 1 to validate without persisting."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateRisk"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created"
          },
          "400": {
            "description": "Validation failed (incl. `domain_mismatch`)"
          },
          "404": {
            "description": "Not found (incl. `subject_not_found`)"
          },
          "409": {
            "description": "Idempotency conflict or write lock busy"
          }
        }
      }
    },
    "/v1/risks/{id}": {
      "patch": {
        "tags": [
          "Risks"
        ],
        "summary": "Patch a risk",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "X-Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200
            }
          },
          {
            "name": "X-Dry-Run",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ]
            },
            "description": "Set to 1 to validate without persisting."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PatchRisk"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated"
          },
          "400": {
            "description": "Validation failed (incl. `domain_mismatch`)"
          },
          "404": {
            "description": "Not found"
          }
        }
      },
      "delete": {
        "tags": [
          "Risks"
        ],
        "summary": "Delete a risk",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "X-Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200
            }
          },
          {
            "name": "X-Dry-Run",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ]
            },
            "description": "Set to 1 to validate without persisting."
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted"
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    },
    "/v1/risks/bulk": {
      "post": {
        "tags": [
          "Risks"
        ],
        "summary": "Bulk-create risks (max 100, per-row domain validation)",
        "parameters": [
          {
            "name": "X-Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200
            }
          },
          {
            "name": "X-Dry-Run",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ]
            },
            "description": "Set to 1 to validate without persisting."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BulkCreateRisks"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created"
          },
          "400": {
            "description": "Validation failed (incl. `domain_mismatch`)"
          },
          "404": {
            "description": "Not found (incl. `subject_not_found`)"
          },
          "409": {
            "description": "Idempotency conflict or write lock busy"
          }
        }
      }
    },
    "/v1/controls": {
      "get": {
        "tags": [
          "Controls"
        ],
        "summary": "List controls (supports ?domain=)",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          },
          {
            "name": "domain",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "ai",
                "cyber",
                "all"
              ],
              "default": "all"
            },
            "description": "Filter by registry domain. `ai` = AI Governance rows, `cyber` = Cybersecurity rows, `all` (default) = both. Tokens carrying only `raic:cyber:*` are forced to `cyber` regardless."
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              }
            }
          }
        }
      }
    },
    "/v1/controls/{id}": {
      "patch": {
        "tags": [
          "Controls"
        ],
        "summary": "Patch a control (narrow surface; accepts `domain` + `control_domain`)",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "X-Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200
            }
          },
          {
            "name": "X-Dry-Run",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ]
            },
            "description": "Set to 1 to validate without persisting."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PatchControl"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated"
          },
          "400": {
            "description": "Validation failed (incl. `domain_mismatch`)"
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    },
    "/v1/use-cases": {
      "get": {
        "tags": [
          "Use Cases"
        ],
        "summary": "List use cases (supports ?domain=)",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          },
          {
            "name": "domain",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "ai",
                "cyber",
                "all"
              ],
              "default": "all"
            },
            "description": "Filter by registry domain. `ai` = AI Governance rows, `cyber` = Cybersecurity rows, `all` (default) = both. Tokens carrying only `raic:cyber:*` are forced to `cyber` regardless."
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              }
            }
          }
        }
      }
    },
    "/v1/use-cases/{id}": {
      "patch": {
        "tags": [
          "Use Cases"
        ],
        "summary": "Patch a use case (narrow surface; accepts `domain`)",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "X-Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200
            }
          },
          {
            "name": "X-Dry-Run",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ]
            },
            "description": "Set to 1 to validate without persisting."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PatchUseCase"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated"
          },
          "400": {
            "description": "Validation failed (incl. `domain_mismatch`)"
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    },
    "/v1/discovery/apps": {
      "get": {
        "tags": [
          "Discovery"
        ],
        "summary": "List discovered AI apps",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              }
            }
          }
        }
      }
    },
    "/v1/suppliers": {
      "get": {
        "tags": [
          "Suppliers"
        ],
        "summary": "List suppliers",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Suppliers"
        ],
        "summary": "Create a supplier",
        "parameters": [
          {
            "name": "X-Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200
            }
          },
          {
            "name": "X-Dry-Run",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ]
            },
            "description": "Set to 1 to validate without persisting."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateSupplier"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created"
          },
          "400": {
            "description": "Validation failed (incl. `domain_mismatch`)"
          },
          "404": {
            "description": "Not found (incl. `subject_not_found`)"
          },
          "409": {
            "description": "Idempotency conflict or write lock busy"
          }
        }
      }
    },
    "/v1/suppliers/{id}": {
      "patch": {
        "tags": [
          "Suppliers"
        ],
        "summary": "Patch a supplier",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "X-Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200
            }
          },
          {
            "name": "X-Dry-Run",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ]
            },
            "description": "Set to 1 to validate without persisting."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PatchSupplier"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated"
          },
          "400": {
            "description": "Validation failed (incl. `domain_mismatch`)"
          },
          "404": {
            "description": "Not found"
          }
        }
      },
      "delete": {
        "tags": [
          "Suppliers"
        ],
        "summary": "Delete a supplier",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "X-Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200
            }
          },
          {
            "name": "X-Dry-Run",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ]
            },
            "description": "Set to 1 to validate without persisting."
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted"
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    },
    "/v1/audit": {
      "get": {
        "tags": [
          "Audit"
        ],
        "summary": "List audit-log entries (scope: raic:audit:read)",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              }
            }
          }
        }
      }
    },
    "/v1/maturity/snapshots": {
      "get": {
        "tags": [
          "Maturity"
        ],
        "summary": "List ai_maturity_snapshots (per domain)",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          },
          {
            "name": "domain",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "ai",
                "cyber",
                "all"
              ],
              "default": "all"
            },
            "description": "Filter by registry domain. `ai` = AI Governance rows, `cyber` = Cybersecurity rows, `all` (default) = both. Tokens carrying only `raic:cyber:*` are forced to `cyber` regardless."
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_domain`."
          }
        }
      }
    },
    "/v1/maturity/snapshots/latest": {
      "get": {
        "tags": [
          "Maturity"
        ],
        "summary": "Latest snapshot for a domain (?domain=ai or ?domain=cyber required)",
        "parameters": [
          {
            "name": "domain",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "ai",
                "cyber",
                "all"
              ],
              "default": "all"
            },
            "description": "Filter by registry domain. `ai` = AI Governance rows, `cyber` = Cybersecurity rows, `all` (default) = both. Tokens carrying only `raic:cyber:*` are forced to `cyber` regardless."
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          },
          "400": {
            "description": "`domain_required` when ?domain= is omitted or set to `all`."
          },
          "404": {
            "description": "No snapshot for that domain yet."
          }
        }
      }
    },
    "/v1/phishing/click-rate": {
      "get": {
        "tags": [
          "Phishing"
        ],
        "summary": "Aggregated phishing-simulation click rate over a window (default 90 days)",
        "parameters": [
          {
            "name": "window_days",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 365,
              "default": 90
            }
          },
          {
            "name": "as_of",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "YYYY-MM-DD; defaults to today."
          }
        ],
        "responses": {
          "200": {
            "description": "OK — `data.click_rate` is null when there are no eligible campaigns in the window."
          },
          "400": {
            "description": "`invalid_window_days` or `invalid_as_of`."
          }
        }
      }
    },
    "/v1/phishing/simulations": {
      "get": {
        "tags": [
          "Phishing"
        ],
        "summary": "List phishing-simulation campaign rows",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Phishing"
        ],
        "summary": "Record a phishing-simulation campaign",
        "parameters": [
          {
            "name": "X-Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200
            }
          },
          {
            "name": "X-Dry-Run",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ]
            },
            "description": "Set to 1 to validate without persisting."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreatePhishingSimulation"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created"
          },
          "400": {
            "description": "Validation failed (incl. `domain_mismatch`)"
          },
          "404": {
            "description": "Not found (incl. `subject_not_found`)"
          },
          "409": {
            "description": "Idempotency conflict or write lock busy"
          }
        }
      }
    },
    "/v1/phishing/simulations/{id}": {
      "patch": {
        "tags": [
          "Phishing"
        ],
        "summary": "Patch a phishing-simulation row",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "X-Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200
            }
          },
          {
            "name": "X-Dry-Run",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ]
            },
            "description": "Set to 1 to validate without persisting."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PatchPhishingSimulation"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated"
          },
          "400": {
            "description": "Validation failed (incl. `domain_mismatch`)"
          },
          "404": {
            "description": "Not found"
          }
        }
      },
      "delete": {
        "tags": [
          "Phishing"
        ],
        "summary": "Delete a phishing-simulation row",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "X-Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200
            }
          },
          {
            "name": "X-Dry-Run",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ]
            },
            "description": "Set to 1 to validate without persisting."
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted"
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    },
    "/v1/hipaa/baas": {
      "get": {
        "tags": [
          "HIPAA"
        ],
        "summary": "List HIPAA Business-Associate Agreements (gated; 404 when hipaa_mode_enabled is off)",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          },
          {
            "name": "from",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Inclusive lower bound on `expiration_date` (YYYY-MM-DD or ISO-8601 timestamp)."
          },
          {
            "name": "to",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Inclusive upper bound on `expiration_date` (YYYY-MM-DD or ISO-8601 timestamp)."
          }
        ],
        "responses": {
          "200": {
            "description": "OK — supports pagination (`limit`, `offset`, max 200) and inclusive date-range filter on `expiration_date` via `?from=` / `?to=`. `meta` echoes `{ total, limit, offset, from, to, date_field }`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_date` when `from`/`to` is malformed, or `invalid_date_range` when `from > to`."
          },
          "404": {
            "description": "`route_not_found` — HIPAA module not enabled for this org (OrgAdmin toggle `hipaa_mode_enabled` is off, or the caller has no resolvable org)."
          }
        }
      }
    },
    "/v1/hipaa/breaches": {
      "get": {
        "tags": [
          "HIPAA"
        ],
        "summary": "List HIPAA breach register rows (gated; 404 when hipaa_mode_enabled is off)",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          },
          {
            "name": "from",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Inclusive lower bound on `discovery_date` (YYYY-MM-DD or ISO-8601 timestamp)."
          },
          {
            "name": "to",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Inclusive upper bound on `discovery_date` (YYYY-MM-DD or ISO-8601 timestamp)."
          }
        ],
        "responses": {
          "200": {
            "description": "OK — supports pagination (`limit`, `offset`, max 200) and inclusive date-range filter on `discovery_date` via `?from=` / `?to=`. `meta` echoes `{ total, limit, offset, from, to, date_field }`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_date` when `from`/`to` is malformed, or `invalid_date_range` when `from > to`."
          },
          "404": {
            "description": "`route_not_found` — HIPAA module not enabled for this org (OrgAdmin toggle `hipaa_mode_enabled` is off, or the caller has no resolvable org)."
          }
        }
      }
    },
    "/v1/hipaa/safeguards": {
      "get": {
        "tags": [
          "HIPAA"
        ],
        "summary": "List HIPAA Safeguards Workbench rows (gated; 404 when hipaa_mode_enabled is off)",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          },
          {
            "name": "from",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Inclusive lower bound on `updated_at` (YYYY-MM-DD or ISO-8601 timestamp)."
          },
          {
            "name": "to",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Inclusive upper bound on `updated_at` (YYYY-MM-DD or ISO-8601 timestamp)."
          }
        ],
        "responses": {
          "200": {
            "description": "OK — supports pagination (`limit`, `offset`, max 200) and inclusive date-range filter on `updated_at` via `?from=` / `?to=`. `meta` echoes `{ total, limit, offset, from, to, date_field }`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_date` when `from`/`to` is malformed, or `invalid_date_range` when `from > to`."
          },
          "404": {
            "description": "`route_not_found` — HIPAA module not enabled for this org (OrgAdmin toggle `hipaa_mode_enabled` is off, or the caller has no resolvable org)."
          }
        }
      }
    },
    "/v1/policies": {
      "get": {
        "tags": [
          "Policies"
        ],
        "summary": "List governance policies (supports ?domain=ai|cyber, ?status=, ?category=)",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          },
          {
            "name": "domain",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "ai",
                "cyber",
                "all"
              ],
              "default": "all"
            },
            "description": "Filter by registry domain. `ai` = AI Governance rows, `cyber` = Cybersecurity rows, `all` (default) = both. Tokens carrying only `raic:cyber:*` are forced to `cyber` regardless."
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Policies"
        ],
        "summary": "Create a governance policy",
        "parameters": [
          {
            "name": "X-Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200
            }
          },
          {
            "name": "X-Dry-Run",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ]
            },
            "description": "Set to 1 to validate without persisting."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreatePolicy"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created"
          },
          "400": {
            "description": "Validation failed (incl. `domain_mismatch`)"
          },
          "404": {
            "description": "Not found (incl. `subject_not_found`)"
          },
          "409": {
            "description": "Idempotency conflict or write lock busy"
          }
        }
      }
    },
    "/v1/policies/{id}": {
      "get": {
        "tags": [
          "Policies"
        ],
        "summary": "Fetch one governance policy by id",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          },
          "404": {
            "description": "Not found"
          }
        }
      },
      "patch": {
        "tags": [
          "Policies"
        ],
        "summary": "Patch a governance policy",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "X-Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200
            }
          },
          {
            "name": "X-Dry-Run",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ]
            },
            "description": "Set to 1 to validate without persisting."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PatchPolicy"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated"
          },
          "400": {
            "description": "Validation failed (incl. `domain_mismatch`)"
          },
          "404": {
            "description": "Not found"
          }
        }
      },
      "delete": {
        "tags": [
          "Policies"
        ],
        "summary": "Delete a governance policy",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "X-Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200
            }
          },
          {
            "name": "X-Dry-Run",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ]
            },
            "description": "Set to 1 to validate without persisting."
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted"
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    },
    "/v1/ai-catalog/entries": {
      "get": {
        "tags": [
          "AI Catalog"
        ],
        "summary": "List global AI catalog entries (keyset-paginated on (updated_at, id) DESC)",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500,
              "default": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Opaque forward cursor from `meta.next_cursor`."
          },
          {
            "name": "updated_since",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "entry_type",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "web",
                "api",
                "model"
              ]
            }
          },
          {
            "name": "vendor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Envelope"
                }
              }
            }
          },
          "403": {
            "description": "Missing `raic:catalog:read` scope."
          }
        }
      }
    },
    "/v1/ai-catalog/entries/lookup": {
      "get": {
        "tags": [
          "AI Catalog"
        ],
        "summary": "Batch existence check by (domain, app, entry_type)",
        "parameters": [
          {
            "name": "keys",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "base64url-encoded JSON array (≤200 items) of `{domain, app, entry_type}`."
          }
        ],
        "responses": {
          "200": {
            "description": "OK — returns only the matching rows."
          },
          "400": {
            "description": "`missing_keys`, `invalid_keys_encoding`, or `validation_failed`."
          }
        }
      }
    },
    "/v1/ai-catalog/candidates": {
      "get": {
        "tags": [
          "AI Catalog"
        ],
        "summary": "List the caller's curator submissions (SuperAdmin JWTs see all)",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500,
              "default": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "new",
                "promoted",
                "ignored",
                "merged"
              ]
            }
          },
          {
            "name": "intent",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "new",
                "patch",
                "retire"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "post": {
        "tags": [
          "AI Catalog"
        ],
        "summary": "Submit a batch of curator candidates (≤100 rows). Never writes ai_catalog_entries directly — SuperAdmin promotion required.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SubmitCatalogCandidates"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Rows upserted into the triage queue."
          },
          "400": {
            "description": "`validation_failed`."
          }
        }
      }
    },
    "/v1/ai-catalog/candidates/{id}": {
      "patch": {
        "tags": [
          "AI Catalog"
        ],
        "summary": "Append additional evidence to a still-pending candidate row",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PatchCatalogCandidate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Evidence appended."
          },
          "404": {
            "description": "Not found or not owned by caller."
          },
          "409": {
            "description": "`already_resolved` — cannot patch a promoted/ignored/merged row."
          }
        }
      }
    },
    "/v1/shadow-ai/registry-assets:bulk": {
      "post": {
        "tags": [
          "Shadow AI Detection"
        ],
        "summary": "Bulk-ingest registry assets (≤1000 rows). Accept-and-reconcile — returns 202 + job_id.",
        "parameters": [
          {
            "name": "X-Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200
            }
          },
          {
            "name": "X-Dry-Run",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ]
            },
            "description": "Set to 1 to validate without persisting."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RegistryAssetsBulk"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Batch accepted; poll `/v1/shadow-ai/jobs/{jobId}` for per-row results."
          },
          "403": {
            "description": "Missing `raic:shadow-ai:write` scope."
          },
          "422": {
            "description": "`validation_failed` — malformed rows or more than 1000 rows."
          }
        }
      }
    },
    "/v1/shadow-ai/signatures:bulk": {
      "post": {
        "tags": [
          "Shadow AI Detection"
        ],
        "summary": "Bulk-ingest detection signatures (≤1000 rows). Over-broad or uncompilable patterns are rejected per row.",
        "parameters": [
          {
            "name": "X-Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200
            }
          },
          {
            "name": "X-Dry-Run",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ]
            },
            "description": "Set to 1 to validate without persisting."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SignaturesBulk"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Batch accepted; rejected rows are listed in the response."
          },
          "403": {
            "description": "Missing `raic:shadow-ai:write` scope."
          },
          "422": {
            "description": "`validation_failed` or `all_rows_rejected`."
          }
        }
      }
    },
    "/v1/shadow-ai/jobs/{jobId}": {
      "get": {
        "tags": [
          "Shadow AI Detection"
        ],
        "summary": "Poll a bulk ingestion job — counts plus per-row status and error",
        "parameters": [
          {
            "name": "jobId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "format",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "json",
                "csv"
              ]
            },
            "description": "`csv` returns the per-row results as a CSV attachment for triage."
          },
          {
            "name": "rows",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "errors",
                "all"
              ]
            },
            "description": "CSV row scope. Defaults to `errors`."
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          },
          "403": {
            "description": "Missing `raic:shadow-ai:read` scope."
          },
          "404": {
            "description": "`not_found`."
          }
        }
      }
    },
    "/v1/shadow-ai/ruleset": {
      "get": {
        "tags": [
          "Shadow AI Detection"
        ],
        "summary": "Published detector config: Layer 1–2 shared rules plus a Layer 3–4 hash → asset lookup",
        "responses": {
          "200": {
            "description": "OK"
          },
          "403": {
            "description": "Missing `raic:shadow-ai:read` scope."
          }
        }
      }
    },
    "/v1/data/flows": {
      "get": {
        "tags": [
          "Data Governance"
        ],
        "summary": "List data flows (metadata only — payloads are never returned)",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          },
          {
            "name": "direction",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "egress",
                "ingress"
              ]
            }
          },
          {
            "name": "channel",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "since",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "min_severity",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 5
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          },
          "403": {
            "description": "Missing `raic:data:read` scope."
          }
        }
      },
      "post": {
        "tags": [
          "Data Governance"
        ],
        "summary": "Ingest one flow — classify, decide, persist, and return the decision",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DataFlowIngest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Flow recorded with the rendered decision."
          },
          "400": {
            "description": "`validation_failed` — supply `content` or `findings`."
          }
        }
      }
    },
    "/v1/data/flows/batch": {
      "post": {
        "tags": [
          "Data Governance"
        ],
        "summary": "Collector batch ingest (1–100 flows); per-row results, invalid rows are rejected individually",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "flows"
                ],
                "properties": {
                  "flows": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 100,
                    "items": {
                      "$ref": "#/components/schemas/DataFlowIngest"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "`data.accepted` / `data.rejected` / `data.blocked` plus a per-row `results` array."
          }
        }
      }
    },
    "/v1/data/proxy": {
      "post": {
        "tags": [
          "Data Governance"
        ],
        "summary": "In-line enforcement point — returns 200 (allow/transform) or 403 (block/quarantine)",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DataFlowIngest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Allowed; `data.transformed_content` present when the action masked or redacted the payload."
          },
          "403": {
            "description": "Blocked or quarantined by policy; the envelope still carries the full decision."
          }
        }
      }
    },
    "/v1/data/events": {
      "get": {
        "tags": [
          "Data Governance"
        ],
        "summary": "List DLP decisions",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/v1/data/events/{id}/ack": {
      "post": {
        "tags": [
          "Data Governance"
        ],
        "summary": "Acknowledge a DLP decision",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Acknowledged."
          },
          "404": {
            "description": "Not found."
          }
        }
      }
    },
    "/v1/data/policies": {
      "get": {
        "tags": [
          "Data Governance"
        ],
        "summary": "List data policies in priority order",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "post": {
        "tags": [
          "Data Governance"
        ],
        "summary": "Create a data policy",
        "responses": {
          "201": {
            "description": "Created."
          }
        }
      }
    },
    "/v1/data/policies/simulate": {
      "post": {
        "tags": [
          "Data Governance"
        ],
        "summary": "Dry evaluation of a candidate policy set — never persists",
        "responses": {
          "200": {
            "description": "Simulated decision."
          }
        }
      }
    },
    "/v1/data/policies/{id}": {
      "patch": {
        "tags": [
          "Data Governance"
        ],
        "summary": "Patch a data policy (version bumped)",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Updated."
          }
        }
      },
      "delete": {
        "tags": [
          "Data Governance"
        ],
        "summary": "Delete a data policy",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted."
          }
        }
      }
    },
    "/v1/data/vault/{id}": {
      "get": {
        "tags": [
          "Data Governance"
        ],
        "summary": "Decrypt a retained payload sample (JWT callers only; scope `raic:vault:read`)",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Decrypted sample."
          },
          "403": {
            "description": "Missing scope or non-JWT caller."
          },
          "404": {
            "description": "Expired or not found."
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT-or-rai_token"
      }
    },
    "schemas": {
      "Envelope": {
        "type": "object",
        "required": [
          "data",
          "meta",
          "error"
        ],
        "properties": {
          "data": {
            "description": "Response payload (object or array)"
          },
          "meta": {
            "type": "object",
            "description": "Pagination and dry-run hints"
          },
          "error": {
            "type": [
              "string",
              "null"
            ],
            "description": "Error code (null on success)"
          }
        }
      },
      "RegistryAssetsBulk": {
        "type": "object",
        "required": [
          "rows"
        ],
        "properties": {
          "registry": {
            "type": "string",
            "maxLength": 64
          },
          "batch_seq": {
            "type": "string",
            "maxLength": 120
          },
          "rows": {
            "type": "array",
            "minItems": 1,
            "maxItems": 1000,
            "items": {
              "type": "object",
              "required": [
                "registry",
                "registry_uid"
              ],
              "properties": {
                "registry": {
                  "type": "string",
                  "maxLength": 64
                },
                "registry_uid": {
                  "type": "string",
                  "maxLength": 200,
                  "description": "Immutable Hub _id — the key of record."
                },
                "canonical_id": {
                  "type": "string",
                  "nullable": true
                },
                "namespace": {
                  "type": "string",
                  "nullable": true
                },
                "slug": {
                  "type": "string",
                  "nullable": true
                },
                "asset_class": {
                  "type": "string",
                  "nullable": true
                },
                "repo_sha": {
                  "type": "string",
                  "nullable": true
                },
                "license_id": {
                  "type": "string",
                  "nullable": true
                },
                "gated": {
                  "type": "boolean"
                },
                "private": {
                  "type": "boolean"
                },
                "disabled": {
                  "type": "boolean"
                },
                "tags": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                },
                "param_total": {
                  "type": "integer",
                  "nullable": true
                },
                "serialization_formats": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                },
                "provider_mappings": {
                  "type": "array",
                  "items": {
                    "type": "object"
                  }
                },
                "tier": {
                  "type": "string",
                  "nullable": true
                },
                "risk_flags": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                },
                "content_hash": {
                  "type": "string",
                  "nullable": true
                }
              }
            }
          }
        }
      },
      "SignaturesBulk": {
        "type": "object",
        "required": [
          "rows"
        ],
        "properties": {
          "registry": {
            "type": "string",
            "maxLength": 64
          },
          "batch_seq": {
            "type": "string",
            "maxLength": 120
          },
          "rows": {
            "type": "array",
            "minItems": 1,
            "maxItems": 1000,
            "items": {
              "type": "object",
              "required": [
                "sig_type",
                "sig_layer",
                "pattern"
              ],
              "properties": {
                "registry_uid": {
                  "type": "string",
                  "nullable": true,
                  "description": "Null for registry-wide rules."
                },
                "sig_type": {
                  "type": "string",
                  "maxLength": 64
                },
                "sig_layer": {
                  "type": "integer",
                  "minimum": 1,
                  "maximum": 4
                },
                "pattern": {
                  "type": "string",
                  "maxLength": 512
                },
                "match_mode": {
                  "type": "string",
                  "enum": [
                    "exact",
                    "prefix",
                    "suffix",
                    "contains",
                    "regex",
                    "hash"
                  ]
                },
                "telemetry_source": {
                  "type": "string",
                  "nullable": true
                },
                "specificity": {
                  "type": "string",
                  "nullable": true
                },
                "fp_risk": {
                  "type": "string",
                  "nullable": true
                },
                "confidence": {
                  "type": "integer",
                  "minimum": 0,
                  "maximum": 100,
                  "nullable": true
                },
                "version": {
                  "type": "integer",
                  "minimum": 1
                },
                "supersedes_id": {
                  "type": "string",
                  "format": "uuid",
                  "nullable": true
                },
                "evidence_url": {
                  "type": "string",
                  "nullable": true
                }
              }
            }
          }
        }
      },
      "DataFlowIngest": {
        "type": "object",
        "required": [
          "direction",
          "channel",
          "collector"
        ],
        "properties": {
          "direction": {
            "type": "string",
            "enum": [
              "egress",
              "ingress"
            ]
          },
          "channel": {
            "type": "string",
            "maxLength": 60,
            "description": "e.g. `browser`, `api`, `email`, `gateway`."
          },
          "collector": {
            "type": "string",
            "maxLength": 60,
            "description": "Reporting collector id, e.g. `extension`, `graph`, `proxy`."
          },
          "destination_host": {
            "type": "string",
            "maxLength": 255,
            "nullable": true
          },
          "system_id": {
            "type": "string",
            "maxLength": 100,
            "nullable": true
          },
          "model_id": {
            "type": "string",
            "maxLength": 120,
            "nullable": true
          },
          "content_type": {
            "type": "string",
            "maxLength": 120,
            "nullable": true
          },
          "actor_label": {
            "type": "string",
            "maxLength": 200,
            "nullable": true
          },
          "shadow_ai": {
            "type": "boolean"
          },
          "occurred_at": {
            "type": "string",
            "format": "date-time"
          },
          "content": {
            "type": "string",
            "maxLength": 200000,
            "description": "Raw payload to classify. Never persisted unless a matching policy retains a sample."
          },
          "findings": {
            "type": "array",
            "maxItems": 500,
            "description": "Pre-computed findings from a trusted collector; merged with server-side classification.",
            "items": {
              "type": "object"
            }
          }
        }
      },
      "CreateRisk": {
        "type": "object",
        "required": [
          "name",
          "subject_type",
          "subject_id"
        ],
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 500
          },
          "subject_type": {
            "type": "string",
            "enum": [
              "ai_system",
              "use_case",
              "control",
              "supplier",
              "other"
            ]
          },
          "subject_id": {
            "type": "string",
            "format": "uuid"
          },
          "domain": {
            "type": "string",
            "enum": [
              "ai",
              "cyber"
            ],
            "description": "Server default `ai`. Cyber-only tokens are forced to `cyber`."
          },
          "department": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "string",
            "enum": [
              "open",
              "mitigating",
              "accepted",
              "closed"
            ]
          },
          "likelihood": {
            "type": "integer",
            "minimum": 1,
            "maximum": 5
          },
          "impact": {
            "type": "integer",
            "minimum": 1,
            "maximum": 5
          },
          "tier": {
            "type": "string",
            "enum": [
              "Critical",
              "High",
              "Medium",
              "Low"
            ]
          }
        }
      },
      "PatchRisk": {
        "type": "object",
        "additionalProperties": true,
        "properties": {
          "domain": {
            "type": "string",
            "enum": [
              "ai",
              "cyber"
            ]
          }
        }
      },
      "BulkCreateRisks": {
        "type": "object",
        "required": [
          "rows"
        ],
        "properties": {
          "rows": {
            "type": "array",
            "maxItems": 100,
            "items": {
              "$ref": "#/components/schemas/CreateRisk"
            }
          }
        }
      },
      "CreateSupplier": {
        "type": "object",
        "required": [
          "supplier_name"
        ],
        "properties": {
          "supplier_name": {
            "type": "string",
            "maxLength": 300
          },
          "criticality": {
            "type": "string",
            "enum": [
              "low",
              "medium",
              "high",
              "critical"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "suspended",
              "offboarded"
            ]
          }
        }
      },
      "PatchSupplier": {
        "type": "object",
        "additionalProperties": true
      },
      "PatchControl": {
        "type": "object",
        "additionalProperties": true,
        "properties": {
          "domain": {
            "type": "string",
            "enum": [
              "ai",
              "cyber"
            ]
          },
          "control_domain": {
            "type": "string",
            "maxLength": 100
          }
        }
      },
      "PatchUseCase": {
        "type": "object",
        "additionalProperties": true,
        "properties": {
          "domain": {
            "type": "string",
            "enum": [
              "ai",
              "cyber"
            ]
          }
        }
      },
      "CreatePhishingSimulation": {
        "type": "object",
        "required": [
          "conducted_on",
          "recipients",
          "clicks"
        ],
        "properties": {
          "conducted_on": {
            "type": "string",
            "format": "date",
            "description": "YYYY-MM-DD; cannot be in the future."
          },
          "recipients": {
            "type": "integer",
            "minimum": 1
          },
          "clicks": {
            "type": "integer",
            "minimum": 0,
            "description": "Must be ≤ recipients."
          },
          "campaign_name": {
            "type": "string",
            "maxLength": 300,
            "nullable": true
          },
          "provider": {
            "type": "string",
            "nullable": true,
            "enum": [
              "KnowBe4",
              "Proofpoint",
              "Microsoft Attack Simulator",
              "Cofense",
              "Hoxhunt",
              "Other",
              "Manual"
            ]
          },
          "reported": {
            "type": "integer",
            "minimum": 0,
            "nullable": true
          },
          "notes": {
            "type": "string",
            "maxLength": 2000,
            "nullable": true
          }
        }
      },
      "PatchPhishingSimulation": {
        "type": "object",
        "additionalProperties": true,
        "description": "At least one field required. The clicks ≤ recipients invariant is re-checked server-side against the existing row."
      },
      "CreatePolicy": {
        "type": "object",
        "required": [
          "title"
        ],
        "properties": {
          "title": {
            "type": "string",
            "maxLength": 300
          },
          "description": {
            "type": "string",
            "maxLength": 8000
          },
          "version": {
            "type": "string",
            "maxLength": 50
          },
          "category": {
            "type": "string",
            "maxLength": 100
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "active",
              "retired",
              "archived"
            ]
          },
          "domain": {
            "type": "string",
            "enum": [
              "ai",
              "cyber"
            ],
            "description": "Server default `ai`."
          },
          "effective_date": {
            "type": "string",
            "format": "date"
          },
          "review_date": {
            "type": "string",
            "format": "date",
            "nullable": true
          },
          "attestation_frequency": {
            "type": "string",
            "enum": [
              "annually",
              "semi_annually",
              "quarterly",
              "monthly",
              "on_change",
              "never"
            ]
          }
        }
      },
      "PatchPolicy": {
        "type": "object",
        "additionalProperties": false,
        "description": "At least one field required.",
        "properties": {
          "title": {
            "type": "string",
            "maxLength": 300
          },
          "description": {
            "type": "string",
            "maxLength": 8000
          },
          "version": {
            "type": "string",
            "maxLength": 50
          },
          "category": {
            "type": "string",
            "maxLength": 100
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "active",
              "retired",
              "archived"
            ]
          },
          "domain": {
            "type": "string",
            "enum": [
              "ai",
              "cyber"
            ]
          },
          "effective_date": {
            "type": "string",
            "format": "date"
          },
          "review_date": {
            "type": "string",
            "format": "date",
            "nullable": true
          },
          "attestation_frequency": {
            "type": "string",
            "enum": [
              "annually",
              "semi_annually",
              "quarterly",
              "monthly",
              "on_change",
              "never"
            ]
          }
        }
      },
      "SubmitCatalogCandidates": {
        "type": "object",
        "required": [
          "rows"
        ],
        "properties": {
          "rows": {
            "type": "array",
            "minItems": 1,
            "maxItems": 100,
            "items": {
              "type": "object",
              "required": [
                "intent",
                "signal_kind",
                "candidate_value"
              ],
              "properties": {
                "intent": {
                  "type": "string",
                  "enum": [
                    "new",
                    "patch",
                    "retire"
                  ]
                },
                "signal_kind": {
                  "type": "string",
                  "enum": [
                    "web_host",
                    "api_host",
                    "model_id",
                    "agent_ext_id"
                  ]
                },
                "candidate_value": {
                  "type": "string",
                  "maxLength": 500
                },
                "raic_entry_id": {
                  "type": "string",
                  "format": "uuid",
                  "nullable": true,
                  "description": "Required when intent=patch or intent=retire."
                },
                "patch_diff": {
                  "type": "object",
                  "additionalProperties": true,
                  "nullable": true,
                  "description": "Required when intent=patch."
                },
                "retire_reason": {
                  "type": "string",
                  "maxLength": 2000,
                  "nullable": true,
                  "description": "Required when intent=retire."
                },
                "source_url": {
                  "type": "string",
                  "format": "uri",
                  "nullable": true
                },
                "source_kind": {
                  "type": "string",
                  "maxLength": 50,
                  "nullable": true,
                  "description": "e.g. `vendor_docs`, `press_release`, `openai_models_api`."
                },
                "source_snippet": {
                  "type": "string",
                  "maxLength": 4000,
                  "nullable": true
                },
                "source_verified_at": {
                  "type": "string",
                  "format": "date-time",
                  "nullable": true
                },
                "confidence": {
                  "type": "number",
                  "minimum": 0,
                  "maximum": 100,
                  "nullable": true
                },
                "notes": {
                  "type": "string",
                  "maxLength": 2000,
                  "nullable": true
                },
                "sample_signals": {
                  "type": "array",
                  "maxItems": 50,
                  "items": {},
                  "nullable": true
                }
              }
            }
          }
        }
      },
      "PatchCatalogCandidate": {
        "type": "object",
        "additionalProperties": false,
        "description": "At least one field required. Appends a new entry to source_evidence[] and updates last_seen_at.",
        "properties": {
          "source_url": {
            "type": "string",
            "format": "uri",
            "nullable": true
          },
          "source_kind": {
            "type": "string",
            "maxLength": 50,
            "nullable": true
          },
          "source_snippet": {
            "type": "string",
            "maxLength": 4000,
            "nullable": true
          },
          "source_verified_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "confidence": {
            "type": "number",
            "minimum": 0,
            "maximum": 100,
            "nullable": true
          },
          "note": {
            "type": "string",
            "maxLength": 2000,
            "nullable": true
          }
        }
      }
    }
  }
}
