{
  "openapi": "3.1.0",
  "info": {
    "title": "sdlc.cc Privacy Gateway API",
    "version": "1.0.0",
    "description": "The stable customer contract for explicit privacy scrubbing. Raw source text is processed only for the request and is not returned in errors or stored by the control plane."
  },
  "servers": [{ "url": "https://api.sdlc.cc", "description": "Hosted gateway; private customers use their approved HTTPS gateway origin." }],
  "paths": {
    "/v1/messages": {
      "post": {
        "operationId": "createMessage",
        "summary": "Anthropic-compatible message creation with policy applied first",
        "description": "Supported only for POST /v1/messages without a query. The bounded payload is validated and scrubbed before one concrete provider invocation; the gateway never retries or falls back. stream:true returns validated buffered SSE with X-SDLC-Streaming-Mode: buffered, not token-by-token delivery. An attempt begins only after PostgreSQL commits its pre-dispatch obligation. A failed admission returns EVIDENCE_UNAVAILABLE with a correlation ID but no attempt/evidence row and zero dispatch. Requires the separately issued messages scope.",
        "security": [{ "tenantBearer": [] }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MessagesRequest" } } } },
        "responses": {
          "200": { "description": "Bounded validated provider response, or a fully buffered canonical SSE replay when stream:true", "headers": { "X-SDLC-Streaming-Mode": { "description": "Present with value buffered on successful SSE responses.", "schema": { "type": "string", "const": "buffered" } } }, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MessagesResponse" } }, "text/event-stream": { "schema": { "type": "string" } } } },
          "400": { "$ref": "#/components/responses/Error" },
          "401": { "$ref": "#/components/responses/Error" },
          "403": { "$ref": "#/components/responses/Error" },
          "405": { "$ref": "#/components/responses/Error" },
          "413": { "$ref": "#/components/responses/Error" },
          "415": { "$ref": "#/components/responses/Error" },
          "422": { "$ref": "#/components/responses/Error" },
          "429": { "$ref": "#/components/responses/Error" },
          "499": { "$ref": "#/components/responses/Error" },
          "502": { "$ref": "#/components/responses/Error" },
          "503": { "$ref": "#/components/responses/Error" }
        }
      }
    },
    "/v1/responses": {
      "post": {
        "operationId": "createOpenAIResponse",
        "summary": "OpenAI Responses-compatible scrub-forward (Preview)",
        "description": "Preview path for Codex and other Responses clients. Exact POST /v1/responses with no query string: prompt-bearing fields are scrubbed, then the request is forwarded to OpenAI when the gateway host has OPENAI_API_KEY. stream:true, images, and files are refused (UNSUPPORTED_MESSAGE_SHAPE). Not under the durable /v1/messages enforcement lifecycle. Requires the separately issued messages scope. Successful responses include X-SDLC-OpenAI-Mode: scrub-forward-preview.",
        "security": [{ "tenantBearer": [] }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "additionalProperties": true, "description": "OpenAI Responses request body; recognized text surfaces are scrubbed in place." } } } },
        "responses": {
          "200": { "description": "Upstream OpenAI JSON response after request scrub (non-streaming only)", "headers": { "X-SDLC-OpenAI-Mode": { "description": "Present with value scrub-forward-preview on this Preview path.", "schema": { "type": "string", "const": "scrub-forward-preview" } } }, "content": { "application/json": { "schema": { "type": "object", "additionalProperties": true } } } },
          "400": { "$ref": "#/components/responses/Error" },
          "401": { "$ref": "#/components/responses/Error" },
          "403": { "$ref": "#/components/responses/Error" },
          "413": { "$ref": "#/components/responses/Error" },
          "415": { "$ref": "#/components/responses/Error" },
          "422": { "$ref": "#/components/responses/Error" },
          "502": { "$ref": "#/components/responses/Error" },
          "503": { "$ref": "#/components/responses/Error" }
        }
      }
    },
    "/v1/chat/completions": {
      "post": {
        "operationId": "createOpenAIChatCompletion",
        "summary": "OpenAI Chat Completions scrub-forward (Preview)",
        "description": "Preview path for OpenAI Chat Completions clients. Exact POST /v1/chat/completions with no query string: messages/system text and tool arguments are scrubbed, then forwarded to OpenAI when the gateway host has OPENAI_API_KEY. stream:true, images, and files are refused (UNSUPPORTED_MESSAGE_SHAPE). Not under the durable /v1/messages enforcement lifecycle. Requires the separately issued messages scope. Successful responses include X-SDLC-OpenAI-Mode: scrub-forward-preview. Legacy /v1/completions remains UNSUPPORTED_MODEL_WRITE.",
        "security": [{ "tenantBearer": [] }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "additionalProperties": true, "description": "OpenAI Chat Completions request body; recognized text surfaces are scrubbed in place." } } } },
        "responses": {
          "200": { "description": "Upstream OpenAI JSON response after request scrub (non-streaming only)", "headers": { "X-SDLC-OpenAI-Mode": { "description": "Present with value scrub-forward-preview on this Preview path.", "schema": { "type": "string", "const": "scrub-forward-preview" } } }, "content": { "application/json": { "schema": { "type": "object", "additionalProperties": true } } } },
          "400": { "$ref": "#/components/responses/Error" },
          "401": { "$ref": "#/components/responses/Error" },
          "403": { "$ref": "#/components/responses/Error" },
          "413": { "$ref": "#/components/responses/Error" },
          "415": { "$ref": "#/components/responses/Error" },
          "422": { "$ref": "#/components/responses/Error" },
          "502": { "$ref": "#/components/responses/Error" },
          "503": { "$ref": "#/components/responses/Error" }
        }
      }
    },
    "/v1/dlp/scrub": {
      "post": {
        "operationId": "scrubText",
        "summary": "Scrub explicitly supplied text before AI use",
        "security": [{ "tenantBearer": [] }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ScrubRequest" } } } },
        "responses": {
          "200": { "description": "Clean text and aggregate detections", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ScrubResponse" } } } },
          "400": { "$ref": "#/components/responses/Error" },
          "401": { "$ref": "#/components/responses/Error" },
          "403": { "$ref": "#/components/responses/Error" },
          "413": { "$ref": "#/components/responses/Error" },
          "503": { "$ref": "#/components/responses/Error" }
        }
      }
    },
    "/v1/documents/scan": {
      "post": {
        "operationId": "scanDocumentText",
        "summary": "Scan explicitly extracted document text without document identity metadata",
        "description": "For tenant-approved connectors only. The request accepts text and max_chars; document IDs, paths, titles, owners, URLs, and arbitrary connector metadata are rejected.",
        "security": [{ "tenantBearer": [] }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ScrubRequest" } } } },
        "responses": {
          "200": { "description": "Clean extracted text and aggregate detections", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ScrubResponse" } } } },
          "400": { "$ref": "#/components/responses/Error" },
          "401": { "$ref": "#/components/responses/Error" },
          "403": { "$ref": "#/components/responses/Error" },
          "413": { "$ref": "#/components/responses/Error" },
          "503": { "$ref": "#/components/responses/Error" }
        }
      }
    },
    "/v1/policy": {
      "get": {
        "operationId": "getEffectivePolicy",
        "summary": "Read the effective policy for the caller's tenant",
        "security": [{ "tenantBearer": [] }],
        "responses": {
          "200": { "description": "Policy metadata only", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Policy" } } } },
          "401": { "$ref": "#/components/responses/Error" },
          "403": { "$ref": "#/components/responses/Error" },
          "503": { "$ref": "#/components/responses/Error" }
        }
      }
    },
    "/v1/evidence/events": {
      "get": {
        "operationId": "listTenantEnforcementEvidence",
        "summary": "Page durable enforcement evidence for the verified tenant",
        "description": "Admitted-attempt view requiring an explicitly issued evidence:read scope. Tenant identity comes only from the verified API key; tenant_id and attempt selectors are rejected. The cursor is opaque and bound to that verified tenant. Pre-admission EVIDENCE_UNAVAILABLE outages have no attempt and never appear here.",
        "security": [{ "tenantBearer": [] }],
        "parameters": [
          { "$ref": "#/components/parameters/EvidenceCursor" },
          { "$ref": "#/components/parameters/EvidenceLimit" }
        ],
        "responses": {
          "200": { "description": "Metadata-only stages for the verified tenant", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TenantEvidencePage" } } } },
          "400": { "$ref": "#/components/responses/Error" },
          "401": { "$ref": "#/components/responses/Error" },
          "403": { "$ref": "#/components/responses/Error" },
          "503": { "$ref": "#/components/responses/Error" }
        }
      }
    },
    "/v1/admin/enforcement-events": {
      "get": {
        "operationId": "listOperatorTenantEnforcementEvidence",
        "summary": "Page one resolved tenant's enforcement evidence as a platform operator",
        "description": "Platform-operator-only admitted-attempt metadata query. Exactly one resolved tenant is required; there is no all-tenant form and unresolved rows are returned only by the separate unresolved operation. Pre-admission availability outages are not evidence rows.",
        "security": [{ "adminBearer": [] }],
        "parameters": [
          { "$ref": "#/components/parameters/RequiredTenantIDQuery" },
          { "$ref": "#/components/parameters/EvidenceCursor" },
          { "$ref": "#/components/parameters/EvidenceLimit" }
        ],
        "responses": {
          "200": { "description": "Metadata-only stages for the selected resolved tenant", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/OperatorEvidencePage" } } } },
          "400": { "$ref": "#/components/responses/Error" },
          "401": { "$ref": "#/components/responses/Error" },
          "503": { "$ref": "#/components/responses/Error" }
        }
      }
    },
    "/v1/admin/enforcement-unresolved": {
      "get": {
        "operationId": "listOperatorUnresolvedEnforcementEvidence",
        "summary": "Page unresolved rejection evidence as a platform operator",
        "description": "Platform-operator-only and deliberately narrower than resolved tenant evidence. Tenant selectors are rejected. Pre-admission storage outages have no attempt row and therefore appear only in operational availability signals, never in this response.",
        "security": [{ "adminBearer": [] }],
        "parameters": [
          { "$ref": "#/components/parameters/EvidenceCursor" },
          { "$ref": "#/components/parameters/EvidenceLimit" }
        ],
        "responses": {
          "200": { "description": "Metadata-only unresolved rejection stages", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UnresolvedEvidencePage" } } } },
          "400": { "$ref": "#/components/responses/Error" },
          "401": { "$ref": "#/components/responses/Error" },
          "503": { "$ref": "#/components/responses/Error" }
        }
      }
    },
    "/v1/audit/usage": {
      "get": {
        "operationId": "getUsageSummary",
        "summary": "Aggregated request usage for an administrator",
        "description": "Metadata only: counts, latency, token estimates and cost. No prompt or completion text is stored or returned. Omit tenant_id to aggregate every tenant the admin token covers.",
        "security": [{ "adminBearer": [] }],
        "parameters": [
          { "$ref": "#/components/parameters/TenantIDQuery" },
          { "$ref": "#/components/parameters/Since" },
          { "$ref": "#/components/parameters/Until" }
        ],
        "responses": {
          "200": { "description": "Usage aggregation", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UsageSummary" } } } },
          "401": { "$ref": "#/components/responses/Error" },
          "500": { "$ref": "#/components/responses/Error" }
        }
      }
    },
    "/v1/admin/events": {
      "get": {
        "operationId": "searchEvents",
        "summary": "Search privacy events and export them for a SIEM",
        "description": "Metadata-only per-event search. The projection carries no prompt, completion or detector-match text. format=csv returns a CSV attachment for SIEM ingest; null-able columns are empty cells so an ingester can distinguish an unrecorded value from a zero.",
        "security": [{ "adminBearer": [] }],
        "parameters": [
          { "$ref": "#/components/parameters/TenantIDQuery" },
          { "$ref": "#/components/parameters/Since" },
          { "$ref": "#/components/parameters/Until" },
          { "name": "provider", "in": "query", "schema": { "type": "string" }, "description": "Exact match, case-insensitive." },
          { "name": "model", "in": "query", "schema": { "type": "string" }, "description": "Exact match, case-insensitive." },
          { "name": "status", "in": "query", "schema": { "type": "string" }, "description": "Exact match, case-insensitive." },
          { "name": "actor_id", "in": "query", "schema": { "type": "string" }, "description": "Exact match, case-insensitive." },
          { "name": "summary_type", "in": "query", "schema": { "type": "string" }, "description": "Exact match, case-insensitive." },
          { "name": "limit", "in": "query", "schema": { "type": "integer", "minimum": 1, "maximum": 5000, "default": 200 }, "description": "Out-of-range values fall back to the default rather than failing the export." },
          { "name": "format", "in": "query", "schema": { "type": "string", "enum": ["json", "csv"], "default": "json" } }
        ],
        "responses": {
          "200": { "description": "Matching events, newest first", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EventSearchResponse" } }, "text/csv": { "schema": { "type": "string" } } } },
          "400": { "$ref": "#/components/responses/Error" },
          "401": { "$ref": "#/components/responses/Error" },
          "500": { "$ref": "#/components/responses/Error" }
        }
      }
    },
    "/v1/admin/organization": {
      "get": {
        "operationId": "getOrganization",
        "summary": "Read one organization",
        "security": [{ "adminBearer": [] }],
        "parameters": [{ "name": "id", "in": "query", "required": true, "schema": { "type": "string" } }],
        "responses": {
          "200": { "description": "Organization record", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Organization" } } } },
          "400": { "$ref": "#/components/responses/Error" },
          "401": { "$ref": "#/components/responses/Error" },
          "404": { "$ref": "#/components/responses/Error" },
          "500": { "$ref": "#/components/responses/Error" }
        }
      },
      "post": {
        "operationId": "upsertOrganization",
        "summary": "Create or update an organization",
        "security": [{ "adminBearer": [] }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/OrganizationRequest" } } } },
        "responses": {
          "200": { "description": "Stored organization", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Organization" } } } },
          "400": { "$ref": "#/components/responses/Error" },
          "401": { "$ref": "#/components/responses/Error" },
          "500": { "$ref": "#/components/responses/Error" }
        }
      }
    },
    "/v1/admin/policies": {
      "get": {
        "operationId": "listPolicies",
        "summary": "List an organization's policies",
        "security": [{ "adminBearer": [] }],
        "parameters": [{ "name": "organization_id", "in": "query", "required": true, "schema": { "type": "string" } }],
        "responses": {
          "200": { "description": "Policies for the organization", "content": { "application/json": { "schema": { "type": "object", "required": ["policies"], "properties": { "policies": { "type": "array", "items": { "$ref": "#/components/schemas/Policy" } } } } } } },
          "400": { "$ref": "#/components/responses/Error" },
          "401": { "$ref": "#/components/responses/Error" },
          "500": { "$ref": "#/components/responses/Error" }
        }
      },
      "post": {
        "operationId": "createPolicy",
        "summary": "Create a policy",
        "description": "The server assigns the policy id; sending one is rejected. Use PUT to change an existing policy.",
        "security": [{ "adminBearer": [] }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PolicyRequest" } } } },
        "responses": {
          "201": { "description": "Stored policy", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Policy" } } } },
          "400": { "$ref": "#/components/responses/Error" },
          "401": { "$ref": "#/components/responses/Error" },
          "404": { "$ref": "#/components/responses/Error" },
          "500": { "$ref": "#/components/responses/Error" }
        }
      },
      "put": {
        "operationId": "updatePolicy",
        "summary": "Update an existing policy",
        "description": "id is required and must already exist, so an update cannot create a competing enabled policy.",
        "security": [{ "adminBearer": [] }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PolicyRequest" } } } },
        "responses": {
          "200": { "description": "Stored policy", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Policy" } } } },
          "400": { "$ref": "#/components/responses/Error" },
          "401": { "$ref": "#/components/responses/Error" },
          "404": { "$ref": "#/components/responses/Error" },
          "500": { "$ref": "#/components/responses/Error" }
        }
      }
    },
    "/v1/admin/policies/versions": {
      "get": {
        "operationId": "listPolicyVersions",
        "summary": "Read a policy's revision history",
        "description": "Append-only history, newest first. Each revision holds the configuration that was in force and the trusted source of the change; an unknown policy returns an empty list rather than an error. Scoped by organization so a guessed policy id cannot read another tenant's history.",
        "security": [{ "adminBearer": [] }],
        "parameters": [
          { "name": "organization_id", "in": "query", "required": true, "schema": { "type": "string" } },
          { "name": "policy_id", "in": "query", "required": true, "schema": { "type": "string" } }
        ],
        "responses": {
          "200": { "description": "Revisions, newest first", "content": { "application/json": { "schema": { "type": "object", "required": ["versions"], "properties": { "versions": { "type": "array", "items": { "$ref": "#/components/schemas/PolicyVersion" } } } } } } },
          "400": { "$ref": "#/components/responses/Error" },
          "401": { "$ref": "#/components/responses/Error" },
          "500": { "$ref": "#/components/responses/Error" }
        }
      }
    },
    "/v1/admin/detection-reports": {
      "get": {
        "operationId": "listDetectionReports",
        "summary": "List content-free detection attempt reports",
        "description": "Recent detection attempts for one organization when a policy opted into report_detection_attempts. Counts and policy metadata only — never prompt text.",
        "security": [{ "adminBearer": [] }],
        "parameters": [
          { "name": "organization_id", "in": "query", "required": true, "schema": { "type": "string" } },
          { "name": "limit", "in": "query", "required": false, "schema": { "type": "integer", "minimum": 1, "maximum": 200, "default": 50 } }
        ],
        "responses": {
          "200": { "description": "Detection reports, newest first", "content": { "application/json": { "schema": { "type": "object", "required": ["reports", "count"], "properties": { "reports": { "type": "array", "items": { "$ref": "#/components/schemas/DetectionAttemptReport" } }, "count": { "type": "integer" } } } } } },
          "400": { "$ref": "#/components/responses/Error" },
          "401": { "$ref": "#/components/responses/Error" },
          "500": { "$ref": "#/components/responses/Error" }
        }
      }
    },
    "/v1/admin/clients": {
      "get": {
        "operationId": "listClientInventory",
        "summary": "List the clients deployed for a tenant",
        "description": "Every issued key plus the surface and version that last used it. The reported name and version are client-supplied and sanitized before storage: evidence of what checked in, never proof of what is running, and never an input to an access decision. Inventory is per key, not per device \u2014 several installs sharing one key collapse to the most recent check-in. status is active, never_seen (configured but never used, usually a half-finished install) or revoked.",
        "security": [{ "adminBearer": [] }],
        "parameters": [{ "name": "tenant_id", "in": "query", "required": true, "schema": { "type": "string" } }],
        "responses": {
          "200": { "description": "Client inventory", "content": { "application/json": { "schema": { "type": "object", "required": ["clients", "count"], "properties": { "clients": { "type": "array", "items": { "$ref": "#/components/schemas/ClientInventoryEntry" } }, "count": { "type": "integer" } } } } } },
          "400": { "$ref": "#/components/responses/Error" },
          "401": { "$ref": "#/components/responses/Error" },
          "500": { "$ref": "#/components/responses/Error" },
          "503": { "$ref": "#/components/responses/Error" }
        }
      }
    },
    "/v1/admin/keys": {
      "get": {
        "operationId": "listTenantKeys",
        "summary": "List a tenant's API keys",
        "description": "Returns metadata and a safe prefix only. The plaintext key exists in exactly one response, the one that issued it.",
        "security": [{ "adminBearer": [] }],
        "parameters": [{ "name": "tenant_id", "in": "query", "required": true, "schema": { "type": "string" } }],
        "responses": {
          "200": { "description": "Keys for the tenant", "content": { "application/json": { "schema": { "type": "object", "required": ["keys"], "properties": { "keys": { "type": "array", "items": { "$ref": "#/components/schemas/TenantKey" } } } } } } },
          "400": { "$ref": "#/components/responses/Error" },
          "401": { "$ref": "#/components/responses/Error" },
          "500": { "$ref": "#/components/responses/Error" },
          "503": { "$ref": "#/components/responses/Error" }
        }
      },
      "post": {
        "operationId": "issueTenantKey",
        "summary": "Issue a tenant API key",
        "description": "The plaintext is returned once and is never persisted or logged. Capture it at issue time or issue a replacement.",
        "security": [{ "adminBearer": [] }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TenantKeyIssueRequest" } } } },
        "responses": {
          "201": { "description": "Issued key, including its one-time plaintext", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TenantKey" } } } },
          "400": { "$ref": "#/components/responses/Error" },
          "401": { "$ref": "#/components/responses/Error" },
          "404": { "$ref": "#/components/responses/Error" },
          "500": { "$ref": "#/components/responses/Error" },
          "503": { "$ref": "#/components/responses/Error" }
        }
      },
      "delete": {
        "operationId": "revokeTenantKey",
        "summary": "Revoke a tenant API key",
        "description": "Revocation records the operator source and timestamp; a retry cannot overwrite the original attribution.",
        "security": [{ "adminBearer": [] }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "additionalProperties": false, "required": ["id"], "properties": { "id": { "type": "integer", "format": "int64" } } } } } },
        "responses": {
          "200": { "description": "Revoked key metadata", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TenantKey" } } } },
          "400": { "$ref": "#/components/responses/Error" },
          "401": { "$ref": "#/components/responses/Error" },
          "404": { "$ref": "#/components/responses/Error" },
          "500": { "$ref": "#/components/responses/Error" },
          "503": { "$ref": "#/components/responses/Error" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "tenantBearer": { "type": "http", "scheme": "bearer", "bearerFormat": "sk_sdlc_*" },
      "adminBearer": { "type": "http", "scheme": "bearer", "description": "The operator token from SDLC_ADMIN_BEARER. When it is unset the gateway refuses every administrative request rather than serving them unguarded." }
    },
    "responses": { "Error": { "description": "Code-only error; response messages never contain submitted source text", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } },
    "parameters": {
      "TenantIDQuery": { "name": "tenant_id", "in": "query", "schema": { "type": "string" }, "description": "Scope to one tenant. Omit to cover every tenant the admin token reaches." },
      "RequiredTenantIDQuery": { "name": "tenant_id", "in": "query", "required": true, "schema": { "type": "string", "minLength": 1, "maxLength": 128 }, "description": "Exact resolved tenant scope. There is no all-tenant form." },
      "EvidenceCursor": { "name": "cursor", "in": "query", "schema": { "type": "string", "maxLength": 512 }, "description": "Opaque cursor bound to the tenant or unresolved query class that created it." },
      "EvidenceLimit": { "name": "limit", "in": "query", "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 50 }, "description": "Strict page bound; malformed or out-of-range values are rejected." },
      "Since": { "name": "since", "in": "query", "schema": { "type": "integer", "format": "int64" }, "description": "Window start, unix seconds. Default: 7 days ago." },
      "Until": { "name": "until", "in": "query", "schema": { "type": "integer", "format": "int64" }, "description": "Window end, unix seconds. Default: now." }
    },
    "schemas": {
      "TenantEvidencePage": { "type": "object", "required": ["events"], "properties": { "events": { "type": "array", "items": { "$ref": "#/components/schemas/TenantEvidenceEvent" } }, "next_cursor": { "type": "string" } } },
      "OperatorEvidencePage": { "type": "object", "required": ["events"], "properties": { "events": { "type": "array", "items": { "$ref": "#/components/schemas/OperatorEvidenceEvent" } }, "next_cursor": { "type": "string" } } },
      "UnresolvedEvidencePage": { "type": "object", "required": ["events"], "properties": { "events": { "type": "array", "items": { "$ref": "#/components/schemas/UnresolvedEvidenceEvent" } }, "next_cursor": { "type": "string" } } },
      "TenantEvidenceEvent": { "type": "object", "description": "One resolved enforcement stage. Tenant scope is implicit in the verified principal.", "required": ["attempt_id", "correlation_id", "sequence", "route", "mode", "policy_version", "action", "detector_counts", "dispatch_state", "duration_micros", "occurred_at", "stage", "terminal"], "properties": { "attempt_id": { "type": "string" }, "correlation_id": { "type": "string" }, "sequence": { "type": "integer", "minimum": 1 }, "client_reference": { "type": "string" }, "route": { "type": "string", "enum": ["direct", "transparent"] }, "mode": { "type": "string", "enum": ["json", "buffered_sse"] }, "policy_id": { "type": "string" }, "policy_version": { "type": "integer", "minimum": 0 }, "action": { "type": "string" }, "detector_counts": { "type": "object", "additionalProperties": { "type": "integer", "minimum": 0 } }, "dispatch_state": { "type": "string" }, "provider": { "type": "string" }, "model": { "type": "string" }, "duration_micros": { "type": "integer", "format": "int64", "minimum": 0 }, "code": { "type": "string" }, "occurred_at": { "type": "string", "format": "date-time" }, "stage": { "type": "string" }, "outcome": { "type": "string" }, "terminal": { "type": "boolean" } } },
      "OperatorEvidenceEvent": { "allOf": [{ "$ref": "#/components/schemas/TenantEvidenceEvent" }, { "type": "object", "required": ["tenant_reference", "attribution"], "properties": { "tenant_reference": { "type": "string" }, "attribution": { "type": "string", "const": "resolved" } } }] },
      "UnresolvedEvidenceEvent": { "type": "object", "description": "Narrow unresolved rejection stage with no tenant, client, policy, detector, model, or provider fields.", "required": ["attempt_id", "correlation_id", "sequence", "route", "mode", "action", "dispatch_state", "duration_micros", "occurred_at", "stage", "terminal"], "properties": { "attempt_id": { "type": "string" }, "correlation_id": { "type": "string" }, "sequence": { "type": "integer", "minimum": 1 }, "route": { "type": "string", "enum": ["direct", "transparent"] }, "mode": { "type": "string", "enum": ["json", "buffered_sse"] }, "action": { "type": "string", "const": "rejected" }, "dispatch_state": { "type": "string" }, "duration_micros": { "type": "integer", "format": "int64", "minimum": 0 }, "code": { "type": "string" }, "occurred_at": { "type": "string", "format": "date-time" }, "stage": { "type": "string" }, "outcome": { "type": "string" }, "terminal": { "type": "boolean" } } },
      "ScrubRequest": { "type": "object", "additionalProperties": false, "required": ["text"], "properties": { "text": { "type": "string", "minLength": 1, "maxLength": 65536, "description": "Text explicitly selected for AI use." }, "max_chars": { "type": "integer", "minimum": 1, "maximum": 65536 } } },
      "ScrubResponse": { "type": "object", "required": ["clean_text", "redactions", "bytes_in", "bytes_out", "policy_active", "policy_mode", "policy_name", "processed_at"], "properties": { "clean_text": { "type": "string", "description": "The scrubbed copy. It is returned only to the caller." }, "redactions": { "type": "object", "additionalProperties": { "type": "integer", "minimum": 0 } }, "bytes_in": { "type": "integer" }, "bytes_out": { "type": "integer" }, "policy_active": { "type": "boolean" }, "policy_mode": { "type": "string", "enum": ["observe", "warn", "redact", "block"] }, "policy_name": { "type": "string" }, "processed_at": { "type": "string", "format": "date-time" } } },
      "Policy": { "type": "object", "required": ["active", "organization_id", "mode", "name", "detector_classes"], "properties": { "active": { "type": "boolean" }, "organization_id": { "type": "string" }, "id": { "type": "string" }, "group_name": { "type": "string", "description": "Cohort this policy applies to. Empty is the organization default; a group policy overrides it for clients whose key carries that group." }, "mode": { "type": "string", "enum": ["observe", "warn", "redact", "block"] }, "name": { "type": "string" }, "detector_classes": { "type": "array", "items": { "type": "string" } }, "enabled": { "type": "boolean" }, "report_detection_attempts": { "type": "boolean", "default": false, "description": "When true, write content-free detection attempt reports for admin review. Never stores prompt text." }, "updated_at": { "type": "string", "format": "date-time" } } },
      "MessagesRequest": { "type": "object", "additionalProperties": false, "required": ["model", "max_tokens", "messages"], "description": "Bounded Anthropic-shaped request. Direct mode accepts string content. Transparent mode additionally accepts the closed text/tool_use/tool_result block subset documented in docs/ENFORCEMENT_CONTRACT.md.", "properties": { "model": { "type": "string", "maxLength": 128 }, "max_tokens": { "type": "integer", "minimum": 1 }, "messages": { "type": "array", "minItems": 1, "maxItems": 100, "items": { "type": "object", "additionalProperties": false, "required": ["role", "content"], "properties": { "role": { "type": "string", "enum": ["user", "assistant"] }, "content": { "oneOf": [{ "type": "string" }, { "type": "array", "minItems": 1, "items": { "$ref": "#/components/schemas/MessageContentBlock" } }] } } } }, "system": { "oneOf": [{ "type": "string" }, { "type": "array", "minItems": 1, "items": { "$ref": "#/components/schemas/TextContentBlock" } }] }, "stream": { "type": "boolean", "default": false } } },
      "MessageContentBlock": { "oneOf": [{ "$ref": "#/components/schemas/TextContentBlock" }, { "$ref": "#/components/schemas/ToolUseContentBlock" }, { "$ref": "#/components/schemas/ToolResultContentBlock" }] },
      "TextContentBlock": { "type": "object", "additionalProperties": false, "required": ["type", "text"], "properties": { "type": { "type": "string", "const": "text" }, "text": { "type": "string" } } },
      "ToolUseContentBlock": { "type": "object", "additionalProperties": false, "required": ["type", "id", "name", "input"], "properties": { "type": { "type": "string", "const": "tool_use" }, "id": { "type": "string", "maxLength": 64 }, "name": { "type": "string", "maxLength": 64 }, "input": { "type": "object" } } },
      "ToolResultContentBlock": { "type": "object", "additionalProperties": false, "required": ["type", "tool_use_id", "content"], "properties": { "type": { "type": "string", "const": "tool_result" }, "tool_use_id": { "type": "string", "maxLength": 64 }, "content": { "oneOf": [{ "type": "string" }, { "type": "array", "minItems": 1, "items": { "$ref": "#/components/schemas/TextContentBlock" } }] } } },
      "MessagesResponse": { "type": "object", "required": ["id", "type", "role", "content", "model"], "properties": { "id": { "type": "string" }, "type": { "type": "string" }, "role": { "type": "string" }, "content": { "type": "array", "items": { "type": "object", "required": ["type", "text"], "properties": { "type": { "type": "string" }, "text": { "type": "string" } } } }, "model": { "type": "string" }, "stop_reason": { "type": "string" }, "usage": { "type": "object", "properties": { "input_tokens": { "type": "integer" }, "output_tokens": { "type": "integer" } } } } },
      "UsageSummary": { "type": "object", "required": ["total_requests", "total_cost_usd_micros", "by_provider", "by_status", "rows"], "properties": { "total_requests": { "type": "integer" }, "total_cost_usd_micros": { "type": "integer", "format": "int64" }, "by_provider": { "type": "array", "items": { "$ref": "#/components/schemas/UsageBucket" } }, "by_status": { "type": "array", "items": { "$ref": "#/components/schemas/UsageBucket" } }, "rows": { "type": "array", "items": { "$ref": "#/components/schemas/Event" } } } },
      "UsageBucket": { "type": "object", "required": ["key", "count", "cost_usd_micros"], "properties": { "key": { "type": "string" }, "count": { "type": "integer" }, "cost_usd_micros": { "type": "integer", "format": "int64" } } },
      "EventSearchResponse": { "type": "object", "required": ["events", "count", "truncated"], "properties": { "events": { "type": "array", "items": { "$ref": "#/components/schemas/Event" } }, "count": { "type": "integer" }, "truncated": { "type": "boolean", "description": "True when the limit cut the result, so a console can distinguish \"no more events\" from \"narrow your filters\"." } } },
      "Event": { "type": "object", "description": "One privacy decision, metadata only. No prompt, completion or detector-match text is recorded.", "properties": { "id": { "type": "integer", "format": "int64" }, "tenant_id": { "type": "string" }, "actor_id": { "type": "string" }, "provider": { "type": "string" }, "model": { "type": "string" }, "summary_type": { "type": "string" }, "status": { "type": "string" }, "error_code": { "type": "string" }, "latency_ms": { "type": "integer" }, "prompt_tokens": { "type": ["integer", "null"] }, "completion_tokens": { "type": ["integer", "null"] }, "cost_usd_micros": { "type": ["integer", "null"], "format": "int64" }, "cached": { "type": "boolean" }, "created_at": { "type": "string", "format": "date-time" } } },
      "Organization": { "type": "object", "required": ["id", "name", "admin_email", "deployment_mode"], "properties": { "id": { "type": "string" }, "name": { "type": "string" }, "admin_email": { "type": "string", "format": "email" }, "deployment_mode": { "type": "string" }, "created_at": { "type": "string", "format": "date-time" }, "updated_at": { "type": "string", "format": "date-time" } } },
      "OrganizationRequest": { "type": "object", "additionalProperties": false, "required": ["id", "name", "admin_email"], "properties": { "id": { "type": "string" }, "name": { "type": "string" }, "admin_email": { "type": "string", "format": "email" }, "deployment_mode": { "type": "string" } } },
      "PolicyRequest": { "type": "object", "additionalProperties": false, "required": ["organization_id", "name", "mode", "detector_classes"], "properties": { "id": { "type": "string", "description": "Required for PUT, rejected on POST." }, "group_name": { "type": "string", "maxLength": 64, "description": "Cohort this policy applies to. Empty (default) means organization-wide. Matched exactly, after trimming, against the group on a client's key." }, "organization_id": { "type": "string" }, "name": { "type": "string" }, "mode": { "type": "string", "enum": ["observe", "warn", "redact", "block"] }, "detector_classes": { "type": "array", "items": { "type": "string" } }, "custom_patterns": { "type": "array", "items": { "$ref": "#/components/schemas/CustomPattern" } }, "enabled": { "type": "boolean", "default": true }, "report_detection_attempts": { "type": "boolean", "default": false, "description": "When true, write content-free detection attempt reports for admin review. Never stores prompt text." } } },
      "PolicyVersion": { "type": "object", "description": "One immutable policy revision. Configuration metadata only.", "required": ["policy_id", "organization_id", "version", "name", "mode", "detector_classes", "enabled", "changed_by", "changed_at"], "properties": { "policy_id": { "type": "string" }, "group_name": { "type": "string" }, "organization_id": { "type": "string" }, "version": { "type": "integer", "minimum": 1, "description": "Numbered per policy, starting at 1." }, "name": { "type": "string" }, "mode": { "type": "string", "enum": ["observe", "warn", "redact", "block"] }, "detector_classes": { "type": "array", "items": { "type": "string" } }, "custom_patterns": { "type": "array", "items": { "$ref": "#/components/schemas/CustomPattern" } }, "enabled": { "type": "boolean" }, "report_detection_attempts": { "type": "boolean", "default": false, "description": "When true, write content-free detection attempt reports for admin review. Never stores prompt text." }, "changed_by": { "type": "string", "description": "Trusted source of the write, such as admin-console or migration-backfill. Not an end-user identity and never client-supplied." }, "changed_at": { "type": "string", "format": "date-time" } } },
      "DetectionAttemptReport": { "type": "object", "description": "One content-free detection attempt. No prompt, completion, or match text.", "required": ["id", "organization_id", "policy_mode", "surface", "decision", "detector_counts", "total_detections", "created_at"], "properties": { "id": { "type": "string" }, "organization_id": { "type": "string" }, "policy_id": { "type": "string" }, "policy_name": { "type": "string" }, "policy_mode": { "type": "string", "enum": ["observe", "warn", "redact", "block"] }, "surface": { "type": "string" }, "decision": { "type": "string" }, "detector_counts": { "type": "object", "additionalProperties": { "type": "integer", "minimum": 0 } }, "total_detections": { "type": "integer", "minimum": 0 }, "created_at": { "type": "string", "format": "date-time" } } },
      "CustomPattern": { "type": "object", "additionalProperties": false, "required": ["label", "regex"], "properties": { "label": { "type": "string", "description": "Redaction label; normalized and length-capped at write time." }, "regex": { "type": "string", "description": "Compiled and validated before storage; a pattern that can match empty is rejected." } } },
      "ClientInventoryEntry": { "type": "object", "required": ["key_id", "tenant_id", "label", "prefix", "scopes", "client_name", "client_version", "last_seen_at", "status"], "properties": { "key_id": { "type": "integer", "format": "int64" }, "tenant_id": { "type": "string" }, "label": { "type": "string" }, "prefix": { "type": "string" }, "group": { "type": "string" }, "scopes": { "type": "array", "items": { "type": "string" } }, "client_name": { "type": "string", "description": "Self-reported surface, sanitized at the edge. Empty when the client never reported one." }, "client_version": { "type": "string", "description": "Self-reported version, sanitized at the edge." }, "last_seen_at": { "type": ["string", "null"], "format": "date-time", "description": "Null when the key has never been used." }, "status": { "type": "string", "enum": ["active", "never_seen", "revoked"] }, "revoked_at": { "type": "string", "format": "date-time" } } },
      "TenantKey": { "type": "object", "required": ["id", "tenant_id", "label", "prefix", "scopes"], "properties": { "id": { "type": "integer", "format": "int64" }, "group": { "type": "string", "description": "Cohort this client belongs to. Determines which policy applies; a client cannot claim a different group at request time." }, "tenant_id": { "type": "string" }, "label": { "type": "string" }, "prefix": { "type": "string", "description": "Safe display prefix; never the full key." }, "scopes": { "type": "array", "items": { "type": "string" } }, "plaintext": { "type": "string", "description": "Present only in the issue response." }, "revoked_at": { "type": "string", "format": "date-time" }, "revoked_by": { "type": "string" } } },
      "TenantKeyIssueRequest": { "type": "object", "additionalProperties": false, "required": ["tenant_id", "label"], "properties": { "tenant_id": { "type": "string" }, "label": { "type": "string" }, "group": { "type": "string", "maxLength": 64, "description": "Optional cohort. Empty means the client follows the organization default policy." }, "scopes": { "type": "array", "items": { "type": "string" }, "description": "Least-privilege by default: a managed client receives scrub plus policy:read; messages and evidence:read must each be granted explicitly." } } },
      "Error": { "type": "object", "additionalProperties": false, "required": ["code", "error", "error_details"], "description": "The top-level code and error strings remain for compatibility. Structured details add explicit retryability and a safe correlation handle. No submitted text, provider body, credential, parser detail, or hostile identifier is included.", "properties": { "code": { "$ref": "#/components/schemas/GatewayErrorCode" }, "error": { "type": "string", "description": "Fixed catalog message; source text is never included." }, "error_details": { "$ref": "#/components/schemas/GatewayErrorDetails" } } },
      "GatewayErrorDetails": { "type": "object", "additionalProperties": false, "required": ["code", "message", "retryable", "correlation_id"], "properties": { "code": { "$ref": "#/components/schemas/GatewayErrorCode" }, "message": { "type": "string", "description": "Same fixed catalog message as the legacy error field." }, "retryable": { "type": "boolean", "description": "Explicit classification; it does not authorize automatic replay of a model write." }, "correlation_id": { "type": "string", "description": "Opaque support handle. It can exist before durable admission and is not an attempt receipt." } } },
      "GatewayErrorCode": { "type": "string", "enum": ["BAD_REQUEST", "UNSUPPORTED_METHOD", "UNSUPPORTED_MODEL_WRITE", "UNSUPPORTED_ROUTE", "UNSUPPORTED_MEDIA_TYPE", "UNSUPPORTED_MESSAGE_SHAPE", "PAYLOAD_TOO_LARGE", "UNAUTHORIZED", "INSUFFICIENT_SCOPE", "POLICY_BLOCKED", "POLICY_UNAVAILABLE", "RATE_LIMITED", "QUOTA_EXCEEDED", "QUOTA_UNAVAILABLE", "EVIDENCE_UNAVAILABLE", "AI_UNAVAILABLE", "AI_ERROR", "UPSTREAM_FAILURE", "REQUEST_CANCELLED", "SCRUB_FAILED", "UNSUPPORTED_TRANSPARENT_WRITE", "GATEWAY_ERROR", "audit_query_failed"] }
    }
  }
}
