openapi: 3.0.3
info:
  title: AxonFlow Agent API
  description: >
    REST API for the AxonFlow Agent service - Authentication, Authorization, and
    Static Policy Enforcement Gateway.


    The Agent serves as the entry point for all client requests, providing:

    - **Proxy Mode**: Full request interception and processing through the
    Orchestrator

    - **Gateway Mode**: Pre-check and audit endpoints for SDK-managed LLM calls

    - **MCP (Model Context Protocol)**: Data connector queries and commands

    - **Authentication**: License and token validation

    - **Static Policy Enforcement**: Fast regex-based content filtering


    ## Single Entry Point


    The Agent is the single entry point for all client traffic (ADR-026):

    clients never call the Orchestrator directly — it is an internal service

    and must not be exposed. **Every `/api/v1/*` endpoint is reachable

    through the Agent.** This spec documents the endpoints the Agent serves

    natively; endpoints the Agent transparently proxies to the Orchestrator

    (audit, decisions, overrides, plans/workflows, cost/budgets/usage,

    connectors, LLM-provider management, evidence, and the

    regulatory-compliance families — EU AI Act, SEBI, RBI, OJK/BI/UU-PDP,

    MAS FEAT) are documented in their canonical specs: orchestrator-api.yaml

    for most families, masfeat-api.yaml for MAS FEAT, and policy-api.yaml

    for dynamic policies. They are still called via the Agent host and port.

    The EU AI Act family additionally keeps pointer entries in this spec, and

    a few agent-native subroutes under otherwise-proxied prefixes (audit

    verification, connector cache refresh) are documented here as well.


    ## Authentication


    All endpoints require authentication via:

    - `Authorization: Basic base64(clientId:clientSecret)`: OAuth2-style client
    credentials (recommended)

    - `user_token`: JWT token for user identification (in request body)


    **Note:** `clientSecret` is optional for community/self-hosted deployments.
    `clientId` is recommended for request identification.


    ## Deployment Modes


    - **SaaS**: Hosted by AxonFlow, multi-tenant (`DEPLOYMENT_MODE=saas`)

    - **Enterprise**: Customer-deployed (`DEPLOYMENT_MODE=enterprise`)

    - **Community**: Local development (`DEPLOYMENT_MODE=community`) - bypasses
    license validation
  version: 2.1.0
  contact:
    name: AxonFlow Support
    url: https://getaxonflow.com/support
  license:
    name: Business Source License 1.1
    url: https://github.com/getaxonflow/axonflow/blob/main/LICENSE
servers:
  - url: https://agent.getaxonflow.com
    description: Production (SaaS)
  - url: https://axonflow.example.com
    description: Self-hosted deployment (agent single entry point, ADR-026)
  - url: http://localhost:8080
    description: Local Development
tags:
  - name: Health
    description: Service health and readiness checks
  - name: Proxy Mode
    description: Full request interception and processing
  - name: Gateway Mode
    description: Pre-check and audit for SDK-managed LLM calls
  - name: Decision Mode
    description: |
      Synchronous policy decision endpoint (ADR-056). Called by an
      infrastructure gateway acting as a Policy Enforcement Point (PEP);
      AxonFlow returns a verdict (`allow` / `deny` / `needs_approval`) and
      the PEP enforces it. Same shared-policy engine as Gateway Mode
      pre-check; difference is the caller.
  - name: MCP Connectors
    description: Model Context Protocol data connector operations
  - name: Metrics
    description: Performance monitoring and observability
  - name: HITL
    description: |
      Human-in-the-Loop decision queue (EU AI Act Article 14).
      Route high-risk AI decisions for human review before execution.
  - name: EU AI Act (Proxied)
    description: >
      EU AI Act compliance module (conformity assessments, accuracy

      monitoring, evidence exports). Served by the orchestrator and PROXIED

      through the agent (`/api/v1/euaiact/*`) per the single-entry-point

      architecture — clients call the agent, never the orchestrator.

      Canonical operation schemas live in orchestrator-api.yaml. Enterprise
      only.
  - name: Circuit Breaker
    description: |
      Emergency circuit breaker for AI operations (EU AI Act Article 14).
      Instantly halt AI operations with two-person deactivation requirement.
  - name: OpenAI Compatible
    description: |
      OpenAI-compatible gateway endpoint (Issue #2351). Accepts standard
      OpenAI Chat Completions requests, runs AxonFlow policy checks, forwards
      to the upstream provider, records audit, and returns an OpenAI-compatible
      response. Customers change only `baseURL` in their OpenAI SDK setup.
  - name: Static Policies
    description: |
      Static policy management (ADR-018).
      Read-only access to system-managed policies for SQL injection detection,
      PII detection, and other pattern-based enforcement rules.
  - name: Audit Verification
    description: |
      Non-repudiation verification of the signed decision chain (#2722).
      Read-only endpoints that re-verify per-record Ed25519 signatures and
      the prev_hash linkage of a tenant's decision records, and publish the
      current public signing key for offline verification. These are not
      Policy Enforcement Points (no decision-engine call); they are org-scoped
      via the authenticated request context and read under RLS, so a caller can
      only verify chains/records belonging to its own organization. Available in
      any deployment mode once a usage database is configured; signatures verify
      only when an `AXONFLOW_AUDIT_SIGNING_KEY` is set (otherwise records are
      hash-chained but reported as unsigned).
  - name: MCP Server
    description: |
      Streamable-HTTP MCP server (spec 2025-06-18) exposing AxonFlow
      governance as MCP tools (check_policy, check_output, audit_tool_call,
      list_policies, get_policy_stats, explain_decision + Pro tools).
      Consumed by the Claude Code / Cursor / Codex plugins and any MCP
      client. Authentication is HTTP Basic (org:license-key) — NOT OAuth;
      the `/.well-known/oauth-*` discovery paths deliberately return an
      advisory 404 saying so.
  - name: OTLP Ingest
    description: |
      OpenTelemetry OTLP/HTTP ingest for Claude Code (cowork) telemetry
      (#2832). **Enterprise only** — community builds mount the routes but
      return 501. Org/tenant identity always comes from the authenticated
      license, never from OTLP resource attributes.
paths:
  /health:
    get:
      tags:
        - Health
      summary: Health check
      description: >
        Returns service health status. During startup, returns `status:
        starting`.

        Once fully initialized, returns `status: healthy`.


        This endpoint responds immediately even during initialization, allowing

        ECS/ALB health checks to pass while the service starts up.
      operationId: healthCheck
      responses:
        '200':
          description: Service health status
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HealthResponse'
              examples:
                healthy:
                  summary: Healthy service
                  value:
                    status: healthy
                    service: axonflow-agent
                    timestamp: '2025-01-15T10:30:00Z'
                    version: 1.0.0
                starting:
                  summary: Service starting
                  value:
                    status: starting
                    service: axonflow-agent
                    timestamp: '2025-01-15T10:30:00Z'
                    version: 1.0.0
  /metrics:
    get:
      tags:
        - Metrics
      summary: Get performance metrics
      description: |
        Returns real-time performance metrics including:
        - Request counts (total, success, failed, blocked)
        - Latency percentiles (P50, P95, P99)
        - Per-stage timing (auth, policy, network)
        - Request type breakdown
        - Connector metrics
      operationId: getMetrics
      responses:
        '200':
          description: Performance metrics
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MetricsResponse'
  /prometheus:
    get:
      tags:
        - Metrics
      summary: Prometheus metrics endpoint
      description: Returns metrics in Prometheus exposition format for scraping
      operationId: getPrometheusMetrics
      responses:
        '200':
          description: Prometheus metrics
          content:
            text/plain:
              schema:
                type: string
                example: |
                  # HELP axonflow_agent_requests_total Total requests
                  # TYPE axonflow_agent_requests_total counter
                  axonflow_agent_requests_total{status="success"} 1234
  /api/request:
    post:
      tags:
        - Proxy Mode
      summary: Process client request
      description: >
        Main entry point for Proxy Mode. The Agent:

        1. Validates client license key

        2. Validates user JWT token

        3. Verifies tenant isolation

        4. Evaluates static policies (PII detection, etc.)

        5. Forwards to Orchestrator if allowed

        6. Returns response with policy metadata


        **Use this endpoint when you want AxonFlow to intercept and process all
        LLM requests.**
      operationId: processRequest
      parameters:
        - $ref: '#/components/parameters/LicenseKey'
        - $ref: '#/components/parameters/ClientSecret'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ClientRequest'
            examples:
              sqlQuery:
                summary: SQL query request
                value:
                  query: Show sales data for last quarter
                  user_token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
                  client_id: travel-app-prod
                  request_type: sql
                  context:
                    connector: postgres
              llmChat:
                summary: LLM chat request
                value:
                  query: Summarize the customer feedback
                  user_token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
                  client_id: support-bot
                  request_type: llm_chat
                  context:
                    model_preference: gpt-4
              multiAgentPlan:
                summary: Multi-agent planning request
                value:
                  query: Find flights from NYC to LAX and book a hotel
                  user_token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
                  client_id: travel-planner
                  request_type: multi-agent-plan
                  context:
                    domain: travel
                    execution_mode: parallel
      responses:
        '200':
          description: Request processed successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ClientResponse'
              example:
                success: true
                data:
                  response: Here is the summary of customer feedback...
                result: 'Flight options found: UA123, AA456, DL789'
                plan_id: plan_1234567890_abc123
                metadata:
                  tasks_executed: 3
                  execution_time_ms: 2500
                policy_info:
                  policies_evaluated:
                    - pii-detection
                    - rate-limit
                  static_checks:
                    - ssn_pattern
                    - credit_card
                  processing_time: 45.2ms
                  tenant_id: tenant-123
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          description: Request blocked by policy
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ClientResponse'
              example:
                success: false
                blocked: true
                block_reason: Query contains PII (SSN detected)
                policy_info:
                  policies_evaluated:
                    - pii-ssn
                  static_checks:
                    - ssn_pattern
                  processing_time: 2.1ms
                  tenant_id: tenant-123
        '500':
          $ref: '#/components/responses/InternalError'
  /api/policy/pre-check:
    post:
      tags:
        - Gateway Mode
      summary: Pre-check request before LLM call
      description: >
        Gateway Mode Step 1: Call this endpoint before making your own LLM API
        call.


        The Agent validates the request against policies and returns:

        - `approved: true` if the request is allowed

        - `context_id` to link with the subsequent audit call

        - Optional `approved_data` from MCP connectors

        - Rate limit information


        **`approved_data` is only prefetched for clean approvals** (#2868):

        when the request is blocked, requires HITL approval, or requires

        redaction, connector prefetch is skipped and `approved_data` is

        never populated — governed data is not fetched for a request that

        may not proceed.


        **Context expires after 5 minutes.**


        ## Example Flow

        ```

        1. SDK calls pre-check → gets context_id, approved=true

        2. SDK makes direct LLM call (OpenAI, Anthropic, etc.)

        3. SDK calls audit with context_id and response metadata

        ```
      operationId: gatewayPreCheck
      parameters:
        - $ref: '#/components/parameters/LicenseKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PreCheckRequest'
            examples:
              basic:
                summary: Basic pre-check
                value:
                  query: What is the customer's order status?
                  user_token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
                  client_id: customer-portal
              withDataSources:
                summary: Pre-check with data sources
                value:
                  query: Find flights from NYC to LAX next week
                  user_token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
                  client_id: travel-app
                  data_sources:
                    - amadeus
                  context:
                    departure_date: '2025-01-20'
                    return_date: '2025-01-25'
      responses:
        '200':
          description: Pre-check result
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PreCheckResponse'
              examples:
                approved:
                  summary: Request approved
                  value:
                    context_id: ctx_abc123def456
                    approved: true
                    policies:
                      - pii-detection
                      - rate-limit
                    rate_limit:
                      limit: 1000
                      remaining: 995
                      reset_at: '2025-01-15T11:00:00Z'
                    expires_at: '2025-01-15T10:35:00Z'
                piiRedaction:
                  summary: PII detected - flagged for redaction (default)
                  description: >-
                    Default behavior (PII_ACTION=redact). Request approved but
                    PII will be redacted in response.
                  value:
                    context_id: ctx_abc123def456
                    approved: true
                    requires_redaction: true
                    policies:
                      - pii-ssn
                    expires_at: '2025-01-15T10:35:00Z'
                blocked:
                  summary: Request blocked (when PII_ACTION=block)
                  value:
                    context_id: ctx_abc123def456
                    approved: false
                    policies:
                      - pii-credit-card
                    block_reason: Query contains credit card number
                    expires_at: '2025-01-15T10:35:00Z'
                withData:
                  summary: Approved with data
                  value:
                    context_id: ctx_abc123def456
                    approved: true
                    approved_data:
                      amadeus:
                        rows:
                          - flight_number: UA123
                            departure: '2025-01-20T08:00:00Z'
                            price: 299.99
                        row_count: 5
                        duration_ms: 450
                    policies:
                      - pii-detection
                    expires_at: '2025-01-15T10:35:00Z'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          description: |
            Budget exceeded — a configured cost budget blocks this request
            (Enterprise cost controls).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          description: |
            Community SaaS tenants past the daily request cap (written by
            the auth middleware; shared rate-limit envelope).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RateLimitEnvelope'
        '503':
          description: |
            Circuit breaker is open — an emergency stop matching this
            request's scope is active.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /api/audit/llm-call:
    post:
      tags:
        - Gateway Mode
      summary: Audit LLM call after completion
      description: >
        Gateway Mode Step 2: Call this endpoint after your LLM API call
        completes.


        Records:

        - Token usage for billing and quotas

        - Latency metrics

        - Provider and model information

        - Estimated cost


        **Requires a valid context_id from pre-check (not expired).**
      operationId: auditLLMCall
      parameters:
        - $ref: '#/components/parameters/LicenseKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AuditLLMCallRequest'
            example:
              context_id: ctx_abc123def456
              client_id: travel-app
              response_summary: Found 5 flights matching criteria
              provider: openai
              model: gpt-4
              token_usage:
                prompt_tokens: 150
                completion_tokens: 200
                total_tokens: 350
              latency_ms: 1250
              metadata:
                request_type: travel_search
                cache_hit: false
      responses:
        '200':
          description: Audit recorded
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuditLLMCallResponse'
              example:
                success: true
                audit_id: aud_xyz789
        '400':
          description: Invalid or expired context
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                success: false
                error: Invalid or expired context
        '401':
          $ref: '#/components/responses/Unauthorized'
  /api/v1/decide:
    post:
      tags:
        - Decision Mode
      summary: Policy decision for an infrastructure gateway (PEP)
      description: |
        Decision Mode endpoint (ADR-056 / epic #2426). The customer's
        infrastructure gateway (Policy Enforcement Point) calls this
        endpoint per request to get a verdict (`allow` / `deny` /
        `needs_approval`) and enforces the result. AxonFlow is consulted,
        never on the traffic path.

        The shared-policy engine behind this endpoint is the same engine
        that backs Gateway Mode's `POST /api/policy/pre-check`. The
        difference is the caller: Gateway Mode is called by application
        code; Decision Mode is called by an infrastructure gateway.

        **M1 scope: static policies only** (PII detection, SQL injection,
        dangerous patterns, RBI India PII, compliance categories) to keep
        the inline RPC budget in single-digit milliseconds. Dynamic/custom
        policy support is M2 scope per the epic.

        **OTel trace correlation:** the response carries a W3C-compatible
        32-hex `trace_id`. When the caller passes a `traceparent` header,
        its trace-id is reused so multi-gateway-layer decisions stitch
        into one end-to-end trace. Each decision also emits an OpenTelemetry
        span on the `axonflow.agent.decision` tracer.

        **Available at all tiers** (Community through Enterprise) — the
        policy engine is the same one Gateway Mode uses.
      operationId: decide
      parameters:
        - $ref: '#/components/parameters/AxonflowClient'
        - in: header
          name: traceparent
          description: |
            W3C trace-context header. When present and valid, the trace-id
            is reused in the response so multi-layer decisions correlate
            into one end-to-end trace.
          required: false
          schema:
            type: string
            example: 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DecideRequest'
            examples:
              llmStage:
                summary: LLM-stage decision (most common)
                value:
                  stage: llm
                  caller_identity:
                    gateway_id: llm-gateway-01
                    tenant_id: acme-prod
                  target:
                    type: llm
                    model: gpt-4o
                    provider: openai
                  query: What is the customer's order status?
              toolStage:
                summary: Tool-stage decision (MCP gateway)
                value:
                  stage: tool
                  caller_identity:
                    gateway_id: mcp-gateway-01
                    tenant_id: acme-prod
                  target:
                    type: tool
                    tool: postgres.query
                  query: SELECT name, email FROM customers LIMIT 10
      responses:
        '200':
          description: Decision verdict (allow / deny / needs_approval)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DecideResponse'
              examples:
                allow:
                  summary: Verdict allow
                  value:
                    verdict: allow
                    decision_id: f81d4fae-7dec-11d0-a765-00a0c91e6bf6
                    trace_id: 0af7651916cd43dd8448eb211c80319c
                    stage: llm
                    reasons: []
                    obligations: []
                    evaluated_policies: []
                    expires_at: '2026-05-23T10:35:00Z'
                deny:
                  summary: Verdict deny (SQLi triggered)
                  value:
                    verdict: deny
                    decision_id: a73e5b1c-2b48-4f2e-a3c4-2e8a3b9f8d1e
                    trace_id: 0af7651916cd43dd8448eb211c80319c
                    stage: tool
                    reasons:
                      - SQL injection pattern matched
                    obligations: []
                    evaluated_policies:
                      - sys_sqli_union
                    expires_at: '2026-05-23T10:35:00Z'
                allowWithRedaction:
                  summary: Verdict allow with redact obligation
                  value:
                    verdict: allow
                    decision_id: c4e8f1a2-9d3b-4c7e-b8f1-7a2c3d4e5f6a
                    trace_id: 0af7651916cd43dd8448eb211c80319c
                    stage: llm
                    reasons: []
                    obligations:
                      - type: redact_pii
                        detail: SSN detected in query
                    evaluated_policies:
                      - sys_pii_ssn
                    expires_at: '2026-05-23T10:35:00Z'
        '400':
          description: Request malformed (invalid JSON or missing required fields).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DecideErrorResponse'
        '401':
          description: |
            Authentication failed. In non-community mode the
            `Authorization` header is required and credentials must
            resolve to a valid client.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DecideErrorResponse'
        '403':
          description: |
            Tenant assertion mismatch -- `caller_identity.tenant_id` or
            `caller_identity.org_id` in the body did not match the
            authenticated identity (non-community mode only).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DecideErrorResponse'
        '429':
          description: |
            Community SaaS tenants past the daily request cap. Only
            emitted on the SaaS surface; self-hosted Community and
            Enterprise deployments do not return 429. The 429 is written
            by the auth middleware **before** the decide handler runs, so
            the body is the shared rate-limit envelope — NOT the
            DecideErrorResponse shape.
          headers:
            Retry-After:
              schema:
                type: integer
              description: Seconds until the daily quota window resets.
            X-Axonflow-Tier-Limit:
              schema:
                type: string
              description: The tier limit that was hit.
            X-Axonflow-Upgrade-URL:
              schema:
                type: string
              description: Where to compare/upgrade tiers.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RateLimitEnvelope'
        '503':
          description: |
            Circuit breaker is open. The body carries
            `verdict: "deny"` as the fail-closed default; a fail-open
            PEP adapter should treat 5xx as "PDP degraded" and apply
            its configured posture per ADR-056 §Failure posture.
            A `Retry-After` header is included when the breaker has
            a scheduled expiration.
          headers:
            Retry-After:
              schema:
                type: integer
              description: Seconds until the breaker is scheduled to close.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DecideErrorResponse'
  /v1/chat/completions:
    post:
      tags:
        - OpenAI Compatible
      summary: OpenAI-compatible chat completions with policy enforcement
      description: |
        Accepts a standard OpenAI Chat Completions request, runs AxonFlow
        policy checks via the shared policy engine, forwards to the upstream
        provider, records audit (tokens, cost, latency, policy decision),
        and returns an OpenAI-compatible response.

        The caller passes their upstream provider API key via the
        `X-Provider-Key` header. AxonFlow auth (Basic Auth or community
        mode) is handled by the same `apiAuthMiddleware` as all other
        agent endpoints.

        Streaming (`stream: true`) is not supported in this release and
        returns HTTP 400 with a clear error.
      operationId: chatCompletionsOpenAICompat
      parameters:
        - name: X-Provider-Key
          in: header
          required: true
          description: Upstream LLM provider API key (e.g. OpenAI API key)
          schema:
            type: string
        - name: traceparent
          in: header
          required: false
          description: W3C traceparent header for trace correlation
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ChatCompletionRequest'
            example:
              model: gpt-4o
              messages:
                - role: user
                  content: What is 2+2?
              temperature: 0.7
              max_tokens: 100
      responses:
        '200':
          description: Successful completion
          headers:
            X-AxonFlow-Decision-Id:
              description: UUID correlating this request in audit logs
              schema:
                type: string
                format: uuid
            X-AxonFlow-Trace-Id:
              description: W3C-compatible 32-hex trace ID for OTel correlation
              schema:
                type: string
                pattern: ^[0-9a-f]{32}$
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ChatCompletionResponse'
        '400':
          description: |
            Request validation error or policy denial. Policy denials
            use `type: "policy_violation"` and `code: "policy_denied"`.
            The OpenAI SDK parses this as `openai.BadRequestError`.
          headers:
            X-AxonFlow-Decision-Id:
              description: UUID correlating this request in audit logs
              schema:
                type: string
                format: uuid
            X-AxonFlow-Trace-Id:
              description: W3C-compatible 32-hex trace ID
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OpenAIErrorResponse'
              examples:
                policy_denied:
                  summary: Policy violation (PII detected)
                  value:
                    error:
                      message: 'Request blocked by policy: PII detected'
                      type: policy_violation
                      code: policy_denied
                stream_not_supported:
                  summary: Streaming not supported
                  value:
                    error:
                      message: >-
                        Streaming is not supported in this release. Remove
                        stream: true.
                      type: invalid_request_error
                      code: stream_not_supported
                missing_provider_key:
                  summary: Missing provider key
                  value:
                    error:
                      message: X-Provider-Key header is required.
                      type: invalid_request_error
                      code: missing_provider_key
        '401':
          $ref: '#/components/responses/Unauthorized'
  /api/policies/test:
    post:
      tags:
        - Proxy Mode
      summary: Test policy evaluation
      description: >
        Test how policies would evaluate a query without making an actual
        request.

        Useful for debugging and policy development.
      operationId: testPolicies
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - query
              properties:
                query:
                  type: string
                  description: Query to test
                user_email:
                  type: string
                  description: User email for context
                request_type:
                  type: string
                  description: Request type
            example:
              query: Show me customer SSN 123-45-6789
              user_email: analyst@company.com
              request_type: sql
      responses:
        '200':
          description: Policy test result
          content:
            application/json:
              schema:
                type: object
                properties:
                  blocked:
                    type: boolean
                  reason:
                    type: string
                  triggered_policies:
                    type: array
                    items:
                      type: string
                  checks_performed:
                    type: array
                    items:
                      type: string
                  processing_time_ms:
                    type: number
              example:
                blocked: true
                reason: 'PII detected: SSN pattern found in query'
                triggered_policies:
                  - pii-ssn
                checks_performed:
                  - ssn_pattern
                  - credit_card_pattern
                  - email_pattern
                processing_time_ms: 0.8
  /api/clients:
    get:
      tags:
        - Proxy Mode
      summary: List registered clients
      description: Returns all registered client applications
      operationId: listClients
      responses:
        '200':
          description: List of clients
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Client'
    post:
      tags:
        - Proxy Mode
      summary: Register a new client
      description: Register a new client application
      operationId: createClient
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Client'
      responses:
        '201':
          description: Client created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Client'
        '400':
          $ref: '#/components/responses/BadRequest'
  /mcp/connectors:
    get:
      tags:
        - MCP Connectors
      summary: List the caller's MCP connectors
      description: |
        Returns the MCP connectors the **authenticated tenant** may reach,
        with their health status. Deployment-shared connectors (those
        registered under the wildcard tenancy `*`) are included for every
        tenant; another tenant's connectors are not
        (`platform/agent/mcp_handler.go:612-620`).

        Connectors provide access to external data sources:
        - PostgreSQL
        - Cassandra
        - Salesforce
        - Snowflake
        - Amadeus (travel API)
        - Slack

        **Authentication.** Wrapped in `apiAuthMiddleware`
        (`platform/agent/mcp_handler.go:581`); the tenancy comes from the
        credential, never from a caller-supplied header or path segment.
        Registered for `GET` only — `apiAuthMiddleware` forwards CORS
        preflights unauthenticated, so registering `OPTIONS` would reach the
        handler with no identity in context.

        *(Behaviour change in #3067: this route was previously registered
        with no auth middleware and returned every tenant's connector names,
        types, versions, capabilities, health and raw driver error strings to
        an anonymous caller.)*
      operationId: listMCPConnectors
      security:
        - BasicAuth: []
        - InternalServiceID: []
          InternalServiceToken: []
      responses:
        '200':
          description: List of connectors
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConnectorListResponse'
              example:
                connectors:
                  - name: postgres_main
                    type: postgres
                    version: 1.0.0
                    healthy: true
                    latency_ms: 5
                    capabilities:
                      - query
                      - execute
                  - name: amadeus
                    type: amadeus
                    version: 1.0.0
                    healthy: true
                    latency_ms: 120
                    capabilities:
                      - search_flights
                      - search_hotels
                count: 2
        '401':
          $ref: '#/components/responses/Unauthorized'
        '503':
          description: MCP registry not initialized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                success: false
                error: MCP registry not initialized
  /mcp/connectors/{name}/health:
    get:
      tags:
        - MCP Connectors
      summary: Check connector health
      description: |
        Returns health status for one of the **authenticated tenant's**
        connectors (or a deployment-shared one).

        Naming another tenant's connector returns the same `404` as a
        nonexistent one — there is no existence oracle, and no live
        connection is opened with the other tenant's credentials
        (`platform/agent/mcp_handler.go:665-676`).

        **Authentication.** Wrapped in `apiAuthMiddleware`
        (`platform/agent/mcp_handler.go:584`), `GET` only.

        *(Behaviour change in #3067: this route was previously registered
        with no auth middleware, so an anonymous caller could name any
        tenant's connector and have the agent open a live connection with
        that tenant's decrypted credentials.)*
      operationId: getMCPConnectorHealth
      security:
        - BasicAuth: []
        - InternalServiceID: []
          InternalServiceToken: []
      parameters:
        - name: name
          in: path
          required: true
          description: |
            Connector name. Resolved within the authenticated tenancy, with a
            fallback to the deployment-shared (`*`) tenancy.
          schema:
            type: string
          example: postgres_main
      responses:
        '200':
          description: Connector health status
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConnectorHealthResponse'
              example:
                healthy: true
                latency_ms: 5
                last_check: '2025-01-15T10:30:00Z'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          description: |
            Connector not found **within the caller's tenancy**. Returned
            both for a name that does not exist and for a name that belongs
            to another tenant.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                success: false
                error: Connector not found
        '503':
          description: MCP registry not initialized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                success: false
                error: MCP registry not initialized
  /mcp/resources/query:
    post:
      tags:
        - MCP Connectors
      summary: Execute MCP query (read-only)
      description: >
        Execute a read-only query via an MCP connector.


        This follows the MCP Resource pattern for data retrieval.

        For write operations, use `/mcp/tools/execute`.


        ## Audit Logging


        All MCP queries are automatically logged to the `mcp_query_audits` table
        with:

        - **Request phase**: SQLi detection results, PII blocking decisions

        - **Response phase**: PII redaction details, redacted field paths

        - **Exfiltration checks**: Row counts, volume limit violations

        - **Result**: Success/failure, error messages, duration


        Each audit entry includes `audit_id` for correlation with SDK
        `PolicyInfo`.

        Statement content is stored as SHA256 hash for privacy.
      operationId: mcpQuery
      parameters:
        - $ref: '#/components/parameters/LicenseKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MCPQueryRequest'
            examples:
              sqlQuery:
                summary: SQL query
                value:
                  client_id: analytics-app
                  user_token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
                  connector: postgres_main
                  statement: SELECT * FROM orders WHERE status = $1
                  parameters:
                    '1': completed
                  limit: 100
                  timeout: 10s
              amadeusSearch:
                summary: Flight search
                value:
                  client_id: travel-app
                  user_token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
                  connector: amadeus
                  operation: search_flights
                  parameters:
                    origin: NYC
                    destination: LAX
                    departure_date: '2025-01-20'
                  timeout: 15s
      responses:
        '200':
          description: Query executed successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MCPQueryResponse'
              example:
                success: true
                connector: amadeus
                data:
                  - flight_number: UA123
                    departure: '2025-01-20T08:00:00Z'
                    arrival: '2025-01-20T11:00:00Z'
                    price: 299.99
                row_count: 5
                duration_ms: 450
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: Connector not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          $ref: '#/components/responses/InternalError'
  /mcp/tools/execute:
    post:
      tags:
        - MCP Connectors
      summary: Execute MCP command (write)
      description: >
        Execute a write command via an MCP connector.


        This follows the MCP Tool pattern for data modification.

        For read operations, use `/mcp/resources/query`.


        ## Audit Logging


        All MCP execute operations are automatically logged to the
        `mcp_query_audits` table with:

        - **Request phase**: SQLi detection results, dangerous operation
        blocking

        - **Result**: Rows affected, success/failure, error messages, duration


        Each audit entry includes `audit_id` for correlation. Operation type
        (INSERT, UPDATE, DELETE)

        is stored in the `operation` field.
      operationId: mcpExecute
      parameters:
        - $ref: '#/components/parameters/LicenseKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MCPExecuteRequest'
            example:
              client_id: order-service
              user_token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
              connector: postgres_main
              action: UPDATE
              statement: UPDATE orders SET status = $1 WHERE id = $2
              parameters:
                '1': shipped
                '2': ord-12345
              timeout: 5s
      responses:
        '200':
          description: Command executed successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MCPExecuteResponse'
              example:
                success: true
                connector: postgres_main
                rows_affected: 1
                duration_ms: 15
                message: Update successful
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/InternalError'
  /api/v1/mcp/check-input:
    post:
      tags:
        - MCP Connectors
      summary: Validate MCP input against policies
      description: >
        Validate an MCP request (query or command) against configured policies
        **without executing it**.


        This endpoint enables external orchestrators (LangGraph, CrewAI, custom
        pipelines) to use

        AxonFlow as a policy gate while managing MCP connector execution
        themselves.


        ## Policy Evaluation


        The following policies are evaluated in order:

        1. **Dynamic policies** (if enabled): Rate limits, budgets, time-access,
        role-access via Orchestrator

        2. **Static policies**: SQL injection detection, dangerous query
        blocking, PII detection


        If any policy blocks the request, the response returns `allowed: false`
        with a `block_reason`.


        ## When to Use


        Use `check-input` + `check-output` when your orchestrator manages MCP
        execution natively.

        Use `/mcp/resources/query` or `/mcp/tools/execute` when you want
        AxonFlow to handle both

        policy enforcement and connector execution.


        ## Audit Logging


        All check-input evaluations are logged to the `mcp_query_audits` table
        with

        `operation: "check-input"` for compliance tracking. Audit entries
        include

        `parameters_hash` (SHA-256) and `parameter_count` for forensic analysis.
      operationId: mcpCheckInput
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MCPCheckInputRequest'
            examples:
              cleanQuery:
                summary: Clean SQL query (passes all policies)
                value:
                  client_id: analytics-app
                  user_token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
                  tenant_id: tenant-123
                  connector_type: postgres
                  statement: SELECT name, email FROM users WHERE id = $1
                  parameters:
                    '1': usr-001
                  operation: query
              sqliAttempt:
                summary: SQL injection attempt (blocked)
                value:
                  client_id: analytics-app
                  user_token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
                  tenant_id: tenant-123
                  connector_type: postgres
                  statement: SELECT * FROM users; DROP TABLE users--
      responses:
        '200':
          description: Input passed all policy checks
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MCPCheckInputResponse'
              example:
                allowed: true
                policies_evaluated: 12
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          description: Input blocked by policy
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MCPCheckInputResponse'
              example:
                allowed: false
                block_reason: SQL injection detected in statement
                policies_evaluated: 3
                policy_info:
                  policies_evaluated: 3
                  blocked: true
                  block_reason: SQL injection detected in statement
        '415':
          description: |
            Unregistered `content_type` (ADR-056). The request named a
            content type that no registered detector handles. A canonical
            blocked audit row tagged `content_type_unsupported` is written
            before the error returns — the request is treated as blocked,
            not silently skipped.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '503':
          description: >-
            Dynamic policy evaluator unavailable (only when graceful degradation
            is disabled)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /api/v1/mcp/check-output:
    post:
      tags:
        - MCP Connectors
      summary: Validate MCP output against policies
      description: >
        Validate MCP response data against configured policies **without having
        executed the query

        through AxonFlow**.


        This endpoint enables external orchestrators to apply AxonFlow's
        output-side policy

        enforcement (PII redaction, exfiltration limits, SQLi response scanning)
        to data they

        fetched from MCP connectors independently.


        ## Policy Evaluation


        The following checks are applied in order:

        1. **SQLi response scanning**: Detects SQL injection artifacts in
        response data

        2. **Static response policies**: PII detection and redaction (SSN,
        credit card, Aadhaar, etc.)

        3. **Exfiltration limits** (query-style only): Row count and byte size
        limits


        If PII is detected, the response includes `redacted_data` with masked
        values.

        If exfiltration limits are exceeded, the response returns `allowed:
        false`.


        ## Query vs Execute Responses


        - **Query responses** (`response_data`): Full policy evaluation
        including exfiltration checks

        - **Execute responses** (`message`): SQLi scanning and PII checks only
        (no exfiltration limits)


        ## Audit Logging


        All check-output evaluations are logged to the `mcp_query_audits` table
        with

        `operation: "check-output"` for compliance tracking.
      operationId: mcpCheckOutput
      parameters:
        - $ref: '#/components/parameters/AxonflowClient'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MCPCheckOutputRequest'
            examples:
              cleanData:
                summary: Clean query response (passes all policies)
                value:
                  client_id: analytics-app
                  user_token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
                  tenant_id: tenant-123
                  connector_type: postgres
                  tool: query
                  response_data:
                    - id: 1
                      name: Alice Johnson
                      department: Engineering
                  row_count: 1
              piiData:
                summary: Response containing PII (redacted)
                value:
                  client_id: analytics-app
                  user_token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
                  tenant_id: tenant-123
                  connector_type: postgres
                  response_data:
                    - id: 1
                      name: Alice Johnson
                      ssn: 123-45-6789
                  row_count: 1
              executeResponse:
                summary: Execute-style response (message only)
                value:
                  client_id: analytics-app
                  user_token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
                  tenant_id: tenant-123
                  connector_type: postgres
                  message: 3 rows updated
                  metadata:
                    query: UPDATE users SET status = 'active' WHERE region = 'us'
      responses:
        '200':
          description: Output passed all policy checks (may include redacted data)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MCPCheckOutputResponse'
              examples:
                clean:
                  summary: No issues found
                  value:
                    allowed: true
                    policies_evaluated: 8
                redacted:
                  summary: PII detected and redacted
                  value:
                    allowed: true
                    redacted_data:
                      - id: 1
                        name: Alice Johnson
                        ssn: XXX-XX-6789
                    policies_evaluated: 8
                    policy_info:
                      policies_evaluated: 8
                      blocked: false
                      redactions_applied: 1
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          description: >-
            Output blocked by policy (exfiltration limit exceeded or SQLi
            detected)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MCPCheckOutputResponse'
              example:
                allowed: false
                block_reason: 'Exfiltration limit exceeded: row_count 15000 > limit 10000'
                policies_evaluated: 3
                exfiltration_info:
                  exceeded: true
                  limit_type: rows
                  rows_returned: 15000
                  row_limit: 10000
                  bytes_returned: 5242880
                  byte_limit: 10485760
  /mcp/health:
    get:
      tags:
        - MCP Connectors
      summary: Overall MCP health
      description: >
        Unauthenticated liveness probe.


        ⚠️ **`healthy_count` + `unhealthy_count` do not sum to

        `total_connectors`, by design**
        (`platform/agent/mcp_handler.go:3377-3405`).

        Because this route is anonymous, the live health checks it performs

        are limited to the **deployment-shared** (operator-configured,

        wildcard-tenancy `*`) connectors, while `total_connectors` keeps its

        pre-existing deployment-wide meaning so operator dashboards do not

        silently change scale. A deployment consisting only of tenant-owned

        connectors therefore reports `healthy: true` with zero counts even if

        every backend is down.


        Use the authenticated `GET /mcp/connectors` for per-tenant connector

        health.


        *(Behaviour change in #3067: this route previously opened a live

        connection to **every** tenant's backend on every anonymous GET —

        cross-tenant credential use plus a free amplification lever.)*
      operationId: getMCPHealth
      responses:
        '200':
          description: MCP system health
          content:
            application/json:
              schema:
                type: object
                properties:
                  healthy:
                    type: boolean
                    description: |
                      True when no deployment-shared connector reported
                      unhealthy. Says nothing about tenant-owned connectors.
                  total_connectors:
                    type: integer
                    description: |
                      Deployment-wide cached-connector count
                      (`registry.Count()`) — a different scale from the two
                      counts below.
                  healthy_count:
                    type: integer
                    description: Healthy **deployment-shared** connectors only
                  unhealthy_count:
                    type: integer
                    description: Unhealthy **deployment-shared** connectors only
                  timestamp:
                    type: string
                    format: date-time
              example:
                healthy: true
                total_connectors: 3
                healthy_count: 1
                unhealthy_count: 0
                timestamp: '2026-07-28T10:30:00Z'
        '503':
          description: MCP registry not initialized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                success: false
                error: MCP registry not initialized
  /api/v1/mcp-server:
    post:
      tags:
        - MCP Server
      summary: MCP JSON-RPC endpoint
      description: |
        Single Streamable-HTTP endpoint for the built-in MCP server. Accepts
        MCP JSON-RPC 2.0 messages (`initialize`, `tools/list`, `tools/call`,
        …) and returns JSON-RPC responses. Governance tools exposed:
        `check_policy`, `check_output`, `audit_tool_call`, `list_policies`,
        `get_policy_stats`, `explain_decision`, plus Pro-tier tools.

        Authentication: HTTP Basic `org:license-key` on every request.
        Tier rate limits (Community SaaS) are reported **inside** the
        JSON-RPC result (`result.content[0].text`), not as an HTTP 429.

        The JSON-RPC `check_output` tool result DOES include
        `redacted_message` — unlike the standalone REST
        `/api/v1/mcp/check-output` endpoint (#2870).
      operationId: mcpServerJSONRPC
      parameters:
        - $ref: '#/components/parameters/LicenseKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              description: MCP JSON-RPC 2.0 message
              properties:
                jsonrpc:
                  type: string
                  enum:
                    - '2.0'
                id:
                  description: Request ID (absent for notifications)
                method:
                  type: string
                  example: tools/call
                params:
                  type: object
                  additionalProperties: true
      responses:
        '200':
          description: JSON-RPC response (errors are carried in the JSON-RPC error member)
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'
    get:
      tags:
        - MCP Server
      summary: Not supported (405)
      description: |
        The MCP server does not offer a server-initiated SSE stream; GET
        always returns 405 Method Not Allowed.
      operationId: mcpServerGet
      responses:
        '405':
          description: Method not allowed
    delete:
      tags:
        - MCP Server
      summary: Terminate MCP session
      description: Terminates the MCP session identified by the `Mcp-Session-Id` header.
      operationId: mcpServerDelete
      parameters:
        - $ref: '#/components/parameters/LicenseKey'
        - name: Mcp-Session-Id
          in: header
          required: false
          schema:
            type: string
      responses:
        '200':
          description: Session terminated
        '401':
          $ref: '#/components/responses/Unauthorized'
  /v1/metrics:
    post:
      tags:
        - OTLP Ingest
      summary: OTLP metrics ingest
      description: |
        OTLP/HTTP `ExportMetricsServiceRequest` ingest for Claude Code
        telemetry. Content types: `application/x-protobuf` (default when
        the header is empty), `application/protobuf`, or `application/json`;
        anything else returns **415**. Success returns 200 with an OTLP
        `ExportMetricsServiceResponse` in the request's content type.

        **Enterprise only** — community builds return **501** with
        `{"error": {"code", "message"}}`. Org/tenant comes from the
        authenticated license (Basic auth), never from OTLP resource
        attributes.
      operationId: otlpIngestMetrics
      parameters:
        - $ref: '#/components/parameters/LicenseKey'
      requestBody:
        required: true
        content:
          application/x-protobuf:
            schema:
              type: string
              format: binary
          application/json:
            schema:
              type: object
              additionalProperties: true
      responses:
        '200':
          description: OTLP export response (matching request content type)
        '400':
          description: Malformed OTLP payload
        '401':
          $ref: '#/components/responses/Unauthorized'
        '415':
          description: Unsupported Content-Type
        '501':
          description: Community build — OTLP ingest is an Enterprise feature
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JSONError'
  /v1/logs:
    post:
      tags:
        - OTLP Ingest
      summary: OTLP logs ingest
      description: |
        OTLP/HTTP `ExportLogsServiceRequest` ingest for Claude Code
        telemetry. Same content-type, auth, edition, and error semantics as
        `POST /v1/metrics`.
      operationId: otlpIngestLogs
      parameters:
        - $ref: '#/components/parameters/LicenseKey'
      requestBody:
        required: true
        content:
          application/x-protobuf:
            schema:
              type: string
              format: binary
          application/json:
            schema:
              type: object
              additionalProperties: true
      responses:
        '200':
          description: OTLP export response (matching request content type)
        '400':
          description: Malformed OTLP payload
        '401':
          $ref: '#/components/responses/Unauthorized'
        '415':
          description: Unsupported Content-Type
        '501':
          description: Community build — OTLP ingest is an Enterprise feature
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JSONError'
  /api/v1/connectors/refresh:
    post:
      tags:
        - MCP Connectors
      summary: Refresh the caller's connector caches
      description: >
        Invalidates and refreshes cached connector instances.

        Use this after configuration changes or deployments.


        **The scope depends on which credential you present**

        (`platform/agent/connector_refresh_api.go:184-216`):


        | Credential | Scope | `message` | `tenant_id` |

        |---|---|---|---|

        | Basic auth (tenant) | the authenticated tenant's connectors only |
        `Tenant connector caches refreshed` | present |

        | Internal-service | every tenant's connectors | `All connector caches
        refreshed` | **absent** |


        **Performance impact:** the next request for each evicted connector

        incurs factory-creation overhead.


        **Authentication.** This route is wrapped in `apiAuthMiddleware`

        (`platform/agent/connector_refresh_api.go:123`) and the tenancy comes

        from the authenticated identity — a caller can no longer evict another

        tenant's pool. Supply **either**:


        - `Authorization: Basic base64(clientId:clientSecret)` — the tenant
          lane (no credentials are required when
          `DEPLOYMENT_MODE=community`); or
        - `X-Internal-Service-ID` + `X-Internal-Service-Token` — the
          operator lane, which is the only way to trigger a deployment-wide
          eviction.

        *(Historical note: these four routes were registered with no auth

        middleware at all — #2883. They are authenticated as of #3067; the

        pre-#3067 "deploy behind network-level controls" advisory no longer

        applies.)*


        `stats.cached_connectors` is **structurally 0** on this route: the

        refresh empties the scope immediately before the count is taken. That

        is the success signal, not a failure.
      operationId: refreshAllConnectors
      security:
        - BasicAuth: []
        - InternalServiceID: []
          InternalServiceToken: []
      responses:
        '200':
          description: Connectors refreshed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConnectorRefreshResponse'
              examples:
                tenantCredential:
                  summary: Basic auth — the caller's own pool
                  value:
                    success: true
                    message: Tenant connector caches refreshed
                    scope: all
                    tenant_id: acme-corp
                    duration: 12.345ms
                    stats:
                      cached_connectors: 0
                internalServiceCredential:
                  summary: Internal-service credential — deployment-wide
                  value:
                    success: true
                    message: All connector caches refreshed
                    scope: all
                    duration: 12.345ms
                    stats:
                      cached_connectors: 0
        '401':
          description: |
            No usable credential. Written by the auth middleware as
            `JSONError` (`platform/agent/auth.go:602`), or — when the
            middleware admitted the caller but resolved no tenancy — by the
            handler as `ErrorResponse`
            (`platform/agent/connector_refresh_api.go:148`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                success: false
                error: authenticated tenant required
        '500':
          description: Refresh failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                success: false
                error: 'Failed to refresh connectors: connection refused'
        '503':
          description: Registry not initialized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                success: false
                error: TenantConnectorRegistry not initialized
  /api/v1/connectors/refresh/{tenant_id}:
    post:
      tags:
        - MCP Connectors
      summary: Refresh tenant connector caches
      description: |
        Invalidates and refreshes all cached connector instances for a tenant.
        Use this after updating a tenant's connector configuration.

        **`{tenant_id}` is not a selector.** It is validated against the
        identity `apiAuthMiddleware` resolved and a mismatch is `403`
        (`platform/agent/connector_refresh_api.go:152-156`); the tenancy that
        is actually refreshed always comes from the credential.

        - **Basic auth:** the resolved identity is your licensed org, so this
          route can only ever refresh your own pool.
        - **Internal-service credential:** the resolved identity is whatever
          `X-Tenant-ID` you send (`platform/agent/authenticator.go:127-130`),
          so an operator targets a named tenant by sending that header *and*
          the matching path segment. Naming a tenant in the path with no
          `X-Tenant-ID` is `403`, because the identity then falls back to the
          synthetic `orchestrator-internal` client id.

        `stats.cached_connectors` is **structurally 0** on this route — the
        refresh empties the tenant's scope immediately before the count is
        taken.
      operationId: refreshTenantConnectors
      security:
        - BasicAuth: []
        - InternalServiceID: []
          InternalServiceToken: []
      parameters:
        - name: tenant_id
          in: path
          required: true
          description: |
            The tenant whose connectors should be refreshed. Must equal the
            authenticated tenant — see the description.
          schema:
            type: string
          example: acme-corp
      responses:
        '200':
          description: Tenant connectors refreshed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConnectorRefreshResponse'
              example:
                success: true
                message: Tenant connector caches refreshed
                scope: tenant
                tenant_id: acme-corp
                duration: 2.456ms
                stats:
                  cached_connectors: 0
        '400':
          description: Missing tenant_id
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                success: false
                error: tenant_id is required
        '401':
          description: |
            No usable credential. Written by the auth middleware as
            `JSONError` (`platform/agent/auth.go:602`), or — when the
            middleware admitted the caller but resolved no tenancy — by the
            handler as `ErrorResponse`
            (`platform/agent/connector_refresh_api.go:148`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                success: false
                error: authenticated tenant required
        '403':
          description: |
            The `{tenant_id}` path segment does not match the authenticated
            tenant (`platform/agent/connector_refresh_api.go:152-156`).
            Refused rather than silently downgraded to the caller's own scope,
            so an operator who mistypes a tenant learns about it.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                success: false
                error: tenant_id does not match the authenticated tenant
        '500':
          description: Refresh failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                success: false
                error: 'Failed to refresh tenant connectors: connection refused'
        '503':
          description: Registry not initialized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                success: false
                error: TenantConnectorRegistry not initialized
  /api/v1/connectors/refresh/{tenant_id}/{connector_name}:
    post:
      tags:
        - MCP Connectors
      summary: Refresh specific connector cache
      description: >
        Invalidates and refreshes a specific connector instance for a tenant.

        Use this after updating a single connector's credentials or
        configuration.


        **`{tenant_id}` is not a selector** — same contract as

        `POST /api/v1/connectors/refresh/{tenant_id}`: the segment is

        validated against the authenticated identity and a mismatch is `403`

        (`platform/agent/connector_refresh_api.go:152-156`).


        Unlike the two broader refresh routes, `stats.cached_connectors` here

        is informative: it is the caller's **remaining** cached-connector

        count after this one connector was evicted

        (`platform/agent/connector_refresh_api.go:314,404-408`).
      operationId: refreshConnector
      security:
        - BasicAuth: []
        - InternalServiceID: []
          InternalServiceToken: []
      parameters:
        - name: tenant_id
          in: path
          required: true
          description: |
            The tenant that owns the connector. Must equal the authenticated
            tenant — see the description.
          schema:
            type: string
          example: acme-corp
        - name: connector_name
          in: path
          required: true
          description: The connector name to refresh
          schema:
            type: string
          example: customer-db
      responses:
        '200':
          description: Connector refreshed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConnectorRefreshResponse'
              example:
                success: true
                message: Connector cache refreshed
                scope: connector
                tenant_id: acme-corp
                connector: customer-db
                duration: 0.789ms
                stats:
                  cached_connectors: 1
        '400':
          description: Missing required parameters
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                success: false
                error: tenant_id and connector_name are required
        '401':
          description: |
            No usable credential. Written by the auth middleware as
            `JSONError` (`platform/agent/auth.go:602`), or — when the
            middleware admitted the caller but resolved no tenancy — by the
            handler as `ErrorResponse`
            (`platform/agent/connector_refresh_api.go:148`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                success: false
                error: authenticated tenant required
        '403':
          description: |
            The `{tenant_id}` path segment does not match the authenticated
            tenant (`platform/agent/connector_refresh_api.go:152-156`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                success: false
                error: tenant_id does not match the authenticated tenant
        '500':
          description: Refresh failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                success: false
                error: 'Failed to refresh connector: connection refused'
        '503':
          description: Registry not initialized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                success: false
                error: TenantConnectorRegistry not initialized
  /api/v1/connectors/cache/stats:
    get:
      tags:
        - MCP Connectors
      summary: Get connector cache statistics
      description: >
        Returns the caller's cached-connector count. **The response is

        tenant-scoped** (`platform/agent/connector_refresh_api.go:350-355`):

        `cached_connectors` is the authenticated tenant's own count, not the

        deployment's.


        **The cache-health counters are operator telemetry.** `hits`,

        `misses`, `evictions`, `factory_creations`, `factory_failures`,

        `connection_errors`, `hit_rate_percent`, `last_eviction` and

        `last_factory_create` are deployment-wide figures. They appear here

        **only** inside a `deployment` object, and **only** for the

        internal-service credential

        (`platform/agent/connector_refresh_api.go:356-369`). A tenant caller

        must not read their absence as zero — it has no cache-health data on

        this surface.


        Un-scoped equivalents for five of them are always available to

        operators on `/prometheus` as

        `axonflow_connector_cache_stats{stat="cached_connectors"|"hits"|"misses"|"evictions"|"hit_rate"}`,

        refreshed on every call to this endpoint

        (`platform/agent/connector_refresh_api.go:338-342`).

        `factory_creations`, `factory_failures`, `connection_errors`,

        `last_eviction` and `last_factory_create` have **no** Prometheus

        series — the `deployment` block is their only surface.


        **Authentication.** Wrapped in `apiAuthMiddleware`

        (`platform/agent/connector_refresh_api.go:132`). This route takes no

        `{tenant_id}` path segment, so it never returns `403`.


        *(Historical note: this route was registered with no auth middleware

        at all and served the deployment-wide counters to anonymous callers —

        #2883, where the `evictions` delta was an existence oracle for

        `(tenant, connector)` pairs. Authenticated and scoped as of #3067.)*
      operationId: getConnectorCacheStats
      security:
        - BasicAuth: []
        - InternalServiceID: []
          InternalServiceToken: []
      responses:
        '200':
          description: Cache statistics
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConnectorCacheStats'
              examples:
                tenantCredential:
                  summary: Basic auth — four fields, and only these four
                  value:
                    cached_connectors: 15
                    registry_enabled: true
                    tenant_id: acme-corp
                    timestamp: '2026-07-28T14:25:00.123456Z'
                internalServiceCredential:
                  summary: Internal-service credential — adds the deployment block
                  value:
                    cached_connectors: 0
                    registry_enabled: true
                    tenant_id: orchestrator-internal
                    timestamp: '2026-07-28T14:25:00.123456Z'
                    deployment:
                      cached_connectors: 15
                      hits: 12345
                      misses: 234
                      evictions: 12
                      factory_creations: 246
                      factory_failures: 0
                      connection_errors: 2
                      hit_rate_percent: 98.1
                      last_eviction: '2026-07-28T10:30:00Z'
                      last_factory_create: '2026-07-28T14:20:00Z'
        '401':
          description: |
            No usable credential. Written by the auth middleware as
            `JSONError` (`platform/agent/auth.go:602`), or — when the
            middleware admitted the caller but resolved no tenancy — by the
            handler as `ErrorResponse`
            (`platform/agent/connector_refresh_api.go:148`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                success: false
                error: authenticated tenant required
        '503':
          description: Registry not initialized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                success: false
                error: TenantConnectorRegistry not initialized
  /api/v1/hitl/queue:
    post:
      tags:
        - HITL
      summary: Create HITL approval request
      description: |
        Route a high-risk AI decision for human review.
        EU AI Act Article 14 requires human oversight for high-risk AI systems.

        Requires both `X-Org-ID` and `X-Tenant-ID` headers (stamped by the
        auth middleware) — **400** when either is missing.

        **Enterprise only** — community builds expose only
        `GET /api/v1/hitl/status`.
      operationId: createHITLDecision
      parameters:
        - $ref: '#/components/parameters/LicenseKey'
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/HITLCreateRequest'
      responses:
        '201':
          description: Approval request created
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    $ref: '#/components/schemas/HITLApprovalRequest'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          description: |
            HITL approvals are disabled for the deployment's license tier
            (e.g. an Enterprise build running a Community-tier license).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '429':
          description: |
            Community SaaS pending-approvals cap reached for the tenant.
            Not returned on self-hosted or Enterprise deployments.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    get:
      tags:
        - HITL
      summary: List HITL approval requests
      description: |
        Retrieve approval requests, filtered and paginated. Tenant scoping
        comes from the authenticated request context (RLS), not a query
        parameter.
      operationId: listHITLDecisions
      parameters:
        - $ref: '#/components/parameters/LicenseKey'
        - name: status
          in: query
          description: Comma-separated status filter (e.g. `pending` or `pending,approved`)
          schema:
            type: string
            example: pending
        - name: severity
          in: query
          description: Comma-separated severity filter
          schema:
            type: string
            example: high,critical
        - name: policy_id
          in: query
          schema:
            type: string
        - name: client_id
          in: query
          schema:
            type: string
        - name: user_id
          in: query
          schema:
            type: string
        - name: limit
          in: query
          schema:
            type: integer
            default: 50
        - name: offset
          in: query
          schema:
            type: integer
            default: 0
        - name: order_by
          in: query
          schema:
            type: string
        - name: order_dir
          in: query
          schema:
            type: string
            enum:
              - asc
              - desc
      responses:
        '200':
          description: List of approval requests
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/HITLApprovalRequest'
                  meta:
                    type: object
                    properties:
                      total:
                        type: integer
                        format: int64
                      limit:
                        type: integer
                      offset:
                        type: integer
  /api/v1/hitl/queue/{id}:
    get:
      tags:
        - HITL
      summary: Get HITL approval request
      operationId: getHITLDecision
      parameters:
        - $ref: '#/components/parameters/LicenseKey'
        - name: id
          in: path
          required: true
          description: The request UUID (`request_id`)
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Approval request details
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    $ref: '#/components/schemas/HITLApprovalRequest'
        '400':
          description: Invalid request ID (not a UUID)
        '404':
          description: Request not found
  /api/v1/hitl/queue/{id}/approve:
    post:
      tags:
        - HITL
      summary: Approve HITL request
      description: Approve a pending request to allow AI execution
      operationId: approveHITLDecision
      parameters:
        - $ref: '#/components/parameters/LicenseKey'
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/HITLReviewInput'
      responses:
        '200':
          description: Request approved
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: object
                    properties:
                      status:
                        type: string
                        example: approved
        '400':
          description: Invalid request ID or JSON body
        '404':
          description: Request not found
        '409':
          description: Conflict — request is not pending (already reviewed or expired)
  /api/v1/hitl/queue/{id}/reject:
    post:
      tags:
        - HITL
      summary: Reject HITL request
      description: Reject a pending request to block AI execution
      operationId: rejectHITLDecision
      parameters:
        - $ref: '#/components/parameters/LicenseKey'
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/HITLReviewInput'
      responses:
        '200':
          description: Request rejected
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: object
                    properties:
                      status:
                        type: string
                        example: rejected
        '400':
          description: Invalid request ID or JSON body
        '404':
          description: Request not found
        '409':
          description: Conflict — request is not pending
  /api/v1/hitl/queue/{id}/override:
    post:
      tags:
        - HITL
      summary: Override HITL request
      description: |
        Record an authorized override of a pending request — the caller takes
        responsibility for letting the action proceed outside the normal
        approve/reject flow. Requires a justification for the audit trail.
      operationId: overrideHITLDecision
      parameters:
        - $ref: '#/components/parameters/LicenseKey'
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                justification:
                  type: string
                  description: Required by the service — missing justification returns 400
                authorized_by_id:
                  type: string
                authorized_by_email:
                  type: string
                authorized_by_role:
                  type: string
      responses:
        '200':
          description: Request overridden
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: object
                    properties:
                      status:
                        type: string
                        example: overridden
        '400':
          description: Invalid request ID/JSON, or missing justification
        '404':
          description: Request not found
        '409':
          description: Conflict — another reviewer decided this request first (lost race)
  /api/v1/hitl/queue/{id}/history:
    get:
      tags:
        - HITL
      summary: Get HITL request audit history
      description: >-
        Immutable audit-trail entries for a request
        (create/approve/reject/override/expire actions).
      operationId: getHITLDecisionHistory
      parameters:
        - $ref: '#/components/parameters/LicenseKey'
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: History entries
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/HITLHistoryEntry'
        '400':
          description: Invalid request ID
  /api/v1/hitl/stats:
    get:
      tags:
        - HITL
      summary: Pending-queue statistics
      description: |
        Dashboard summary of the pending queue for the caller's org.
        Requires the `X-Org-ID` header (400 when missing).
      operationId: getHITLStats
      parameters:
        - $ref: '#/components/parameters/LicenseKey'
      responses:
        '200':
          description: Pending statistics
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: object
                    properties:
                      total_pending:
                        type: integer
                        format: int64
                      high_priority:
                        type: integer
                        format: int64
                      critical_priority:
                        type: integer
                        format: int64
                      oldest_pending_hours:
                        type: number
        '400':
          description: Missing X-Org-ID header
  /api/v1/hitl/status:
    get:
      tags:
        - HITL
      summary: HITL feature status
      description: |
        Reports whether HITL is enabled and which features are available.
        This is the **only** HITL endpoint present in community builds
        (returns `enabled: false`, `mode: "community"`); Enterprise builds
        return `enabled: true`, `mode: "enterprise"` plus a feature map
        (queue, approve_reject, override, expiration, audit_history,
        pending_summary, notify_url, idempotency_key).
      operationId: getHITLStatus
      parameters:
        - $ref: '#/components/parameters/LicenseKey'
      responses:
        '200':
          description: Feature status
          content:
            application/json:
              schema:
                type: object
                properties:
                  enabled:
                    type: boolean
                  mode:
                    type: string
                    enum:
                      - community
                      - enterprise
                  features:
                    type: object
                    additionalProperties:
                      type: boolean
  /api/v1/hitl/expire:
    post:
      tags:
        - HITL
      summary: Expire stale pending requests
      description: |
        Sweeps pending requests past their `expires_at` into the `expired`
        state and returns the count. Intended for schedulers/ops automation;
        the platform also expires lazily.
      operationId: expireHITLDecisions
      parameters:
        - $ref: '#/components/parameters/LicenseKey'
      responses:
        '200':
          description: Sweep result
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: object
                    properties:
                      expired_count:
                        type: integer
  /api/v1/circuit-breaker/trip:
    post:
      tags:
        - Circuit Breaker
      summary: Trip circuit breaker (emergency stop)
      description: |
        Immediately halt matching AI operations for the organization
        (EU AI Act Article 14). Also exposed as the alias
        `POST /api/v1/emergency-stop`.

        Identity comes from headers stamped by the auth middleware:
        `X-Org-ID` (falls back to `X-Tenant-ID`) selects the org;
        `X-User-ID` is **required** for the Article 14 audit trail
        (400 when missing). `X-User-Email` is recorded when present.

        **Enterprise only** — community builds register no circuit-breaker
        routes (404).
      operationId: tripCircuitBreaker
      parameters:
        - $ref: '#/components/parameters/LicenseKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CircuitBreakerTripRequest'
      responses:
        '201':
          description: Circuit tripped — matching requests are now blocked
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: object
                    properties:
                      circuit_id:
                        type: string
                      state:
                        type: string
                      scope:
                        type: string
                      scope_id:
                        type: string
                      tripped_at:
                        type: string
                        format: date-time
                      expires_at:
                        type: string
                        format: date-time
                      message:
                        type: string
        '400':
          description: >-
            Missing X-Org-ID/X-User-ID header, invalid JSON, invalid scope, or
            missing scope_id for a non-global scope
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /api/v1/circuit-breaker/reset:
    post:
      tags:
        - Circuit Breaker
      summary: Reset circuit breaker (release emergency stop)
      description: |
        Release an emergency stop and resume normal operations. Also exposed
        as the alias `POST /api/v1/emergency-stop/release`. Requires
        `X-Org-ID` (or `X-Tenant-ID`) and `X-User-ID` headers (400 when
        missing). **Enterprise only.**
      operationId: resetCircuitBreaker
      parameters:
        - $ref: '#/components/parameters/LicenseKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CircuitBreakerResetRequest'
      responses:
        '200':
          description: Circuit reset — normal operations resumed
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: object
                    properties:
                      scope:
                        type: string
                      scope_id:
                        type: string
                      state:
                        type: string
                        example: closed
                      message:
                        type: string
        '400':
          description: Missing X-Org-ID/X-User-ID header or invalid JSON
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /api/v1/circuit-breaker/check:
    post:
      tags:
        - Circuit Breaker
      summary: Check whether a request would be allowed
      description: |
        Evaluates the caller's scope hierarchy (global → tenant → client →
        policy) and reports whether an open circuit would block the request.
        Read-only; does not mutate circuit state. **Enterprise only.**
      operationId: checkCircuitBreaker
      parameters:
        - $ref: '#/components/parameters/LicenseKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                tenant_id:
                  type: string
                client_id:
                  type: string
                policy_id:
                  type: string
      responses:
        '200':
          description: >-
            Check result (allowed, or blocked with the tripping circuit's
            details)
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: object
                    properties:
                      allowed:
                        type: boolean
                      circuit_id:
                        type: string
                        description: Present only when blocked
                      scope:
                        type: string
                      scope_id:
                        type: string
                      reason:
                        type: string
                      tripped_by:
                        type: string
                      tripped_at:
                        type: string
                        format: date-time
                      expires_at:
                        type: string
                        format: date-time
                      comment:
                        type: string
        '400':
          description: Missing X-Org-ID header or invalid JSON
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /api/v1/emergency-stop:
    post:
      tags:
        - Circuit Breaker
      summary: Emergency stop (alias of circuit-breaker trip)
      description: |
        Clearer Article 14 naming for `POST /api/v1/circuit-breaker/trip` —
        identical handler, request body, and responses. **Enterprise only.**
      operationId: emergencyStop
      parameters:
        - $ref: '#/components/parameters/LicenseKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CircuitBreakerTripRequest'
      responses:
        '201':
          description: Emergency stop activated (see the trip endpoint for the body shape)
        '400':
          $ref: '#/components/responses/BadRequest'
  /api/v1/emergency-stop/release:
    post:
      tags:
        - Circuit Breaker
      summary: Release emergency stop (alias of circuit-breaker reset)
      description: |
        Clearer Article 14 naming for `POST /api/v1/circuit-breaker/reset` —
        identical handler, request body, and responses. **Enterprise only.**
      operationId: emergencyStopRelease
      parameters:
        - $ref: '#/components/parameters/LicenseKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CircuitBreakerResetRequest'
      responses:
        '200':
          description: Emergency stop released (see the reset endpoint for the body shape)
        '400':
          $ref: '#/components/responses/BadRequest'
  /api/v1/euaiact/export:
    get:
      tags:
        - EU AI Act (Proxied)
      summary: List compliance evidence exports (proxied to orchestrator)
      description: >-
        Agent-proxied. Canonical contract in orchestrator-api.yaml `GET
        /api/v1/euaiact/export`.
      operationId: euaiactListExportsViaAgent
      parameters:
        - $ref: '#/components/parameters/LicenseKey'
      responses:
        '200':
          description: Export request list (see orchestrator-api.yaml for the schema)
    post:
      tags:
        - EU AI Act (Proxied)
      summary: Create a compliance evidence export (proxied to orchestrator)
      description: >-
        Agent-proxied. Canonical contract in orchestrator-api.yaml `POST
        /api/v1/euaiact/export`.
      operationId: euaiactCreateExportViaAgent
      parameters:
        - $ref: '#/components/parameters/LicenseKey'
      responses:
        '201':
          description: Export request created (see orchestrator-api.yaml for the schema)
  /api/v1/euaiact/export/{export_id}:
    get:
      tags:
        - EU AI Act (Proxied)
      summary: Get an export request (proxied to orchestrator)
      description: Agent-proxied. Canonical contract in orchestrator-api.yaml.
      operationId: euaiactGetExportViaAgent
      parameters:
        - $ref: '#/components/parameters/LicenseKey'
        - name: export_id
          in: path
          required: true
          description: Export request identifier
          schema:
            type: string
      responses:
        '200':
          description: Export request status (see orchestrator-api.yaml for the schema)
  /api/v1/euaiact/export/{export_id}/download:
    get:
      tags:
        - EU AI Act (Proxied)
      summary: Download a completed export (proxied to orchestrator)
      description: Agent-proxied. Canonical contract in orchestrator-api.yaml.
      operationId: euaiactDownloadExportViaAgent
      parameters:
        - $ref: '#/components/parameters/LicenseKey'
        - name: export_id
          in: path
          required: true
          description: Export request identifier
          schema:
            type: string
      responses:
        '200':
          description: >-
            Evidence bundle download (see orchestrator-api.yaml for content
            types)
  /api/v1/euaiact/conformity:
    get:
      tags:
        - EU AI Act (Proxied)
      summary: List conformity assessments (proxied to orchestrator)
      description: >-
        Agent-proxied. Canonical contract in orchestrator-api.yaml `GET
        /api/v1/euaiact/conformity`.
      operationId: euaiactListConformityViaAgent
      parameters:
        - $ref: '#/components/parameters/LicenseKey'
      responses:
        '200':
          description: Assessment list (see orchestrator-api.yaml for the schema)
    post:
      tags:
        - EU AI Act (Proxied)
      summary: Create a conformity assessment (proxied to orchestrator)
      description: >-
        Agent-proxied. Canonical contract in orchestrator-api.yaml `POST
        /api/v1/euaiact/conformity`.
      operationId: euaiactCreateConformityViaAgent
      parameters:
        - $ref: '#/components/parameters/LicenseKey'
      responses:
        '201':
          description: Assessment created (see orchestrator-api.yaml for the schema)
  /api/v1/euaiact/conformity/{assessment_id}:
    get:
      tags:
        - EU AI Act (Proxied)
      summary: Get a conformity assessment (proxied to orchestrator)
      description: Agent-proxied. Canonical contract in orchestrator-api.yaml.
      operationId: euaiactGetConformityViaAgent
      parameters:
        - $ref: '#/components/parameters/LicenseKey'
        - name: assessment_id
          in: path
          required: true
          description: Conformity assessment identifier
          schema:
            type: string
      responses:
        '200':
          description: Assessment detail (see orchestrator-api.yaml for the schema)
    put:
      tags:
        - EU AI Act (Proxied)
      summary: Update a conformity assessment (proxied to orchestrator)
      description: Agent-proxied. Canonical contract in orchestrator-api.yaml.
      operationId: euaiactUpdateConformityViaAgent
      parameters:
        - $ref: '#/components/parameters/LicenseKey'
        - name: assessment_id
          in: path
          required: true
          description: Conformity assessment identifier
          schema:
            type: string
      responses:
        '200':
          description: Assessment updated (see orchestrator-api.yaml for the schema)
  /api/v1/euaiact/conformity/{assessment_id}/submit:
    post:
      tags:
        - EU AI Act (Proxied)
      summary: Submit an assessment for review (proxied to orchestrator)
      description: Agent-proxied. Canonical contract in orchestrator-api.yaml.
      operationId: euaiactSubmitConformityViaAgent
      parameters:
        - $ref: '#/components/parameters/LicenseKey'
        - name: assessment_id
          in: path
          required: true
          description: Conformity assessment identifier
          schema:
            type: string
      responses:
        '200':
          description: Assessment submitted (see orchestrator-api.yaml for the schema)
  /api/v1/euaiact/conformity/{assessment_id}/approve:
    post:
      tags:
        - EU AI Act (Proxied)
      summary: Approve an assessment (proxied to orchestrator)
      description: Agent-proxied. Canonical contract in orchestrator-api.yaml.
      operationId: euaiactApproveConformityViaAgent
      parameters:
        - $ref: '#/components/parameters/LicenseKey'
        - name: assessment_id
          in: path
          required: true
          description: Conformity assessment identifier
          schema:
            type: string
      responses:
        '200':
          description: Assessment approved (see orchestrator-api.yaml for the schema)
  /api/v1/euaiact/conformity/{assessment_id}/reject:
    post:
      tags:
        - EU AI Act (Proxied)
      summary: Reject an assessment (proxied to orchestrator)
      description: Agent-proxied. Canonical contract in orchestrator-api.yaml.
      operationId: euaiactRejectConformityViaAgent
      parameters:
        - $ref: '#/components/parameters/LicenseKey'
        - name: assessment_id
          in: path
          required: true
          description: Conformity assessment identifier
          schema:
            type: string
      responses:
        '200':
          description: Assessment rejected (see orchestrator-api.yaml for the schema)
  /api/v1/euaiact/accuracy:
    get:
      tags:
        - EU AI Act (Proxied)
      summary: Accuracy summary (proxied to orchestrator)
      description: >-
        Agent-proxied. Canonical contract in orchestrator-api.yaml `GET
        /api/v1/euaiact/accuracy`.
      operationId: euaiactAccuracySummaryViaAgent
      parameters:
        - $ref: '#/components/parameters/LicenseKey'
      responses:
        '200':
          description: >-
            Per-model accuracy summary (see orchestrator-api.yaml for the
            schema)
  /api/v1/euaiact/accuracy/record:
    post:
      tags:
        - EU AI Act (Proxied)
      summary: Record an accuracy metric (proxied to orchestrator)
      description: Agent-proxied. Canonical contract in orchestrator-api.yaml.
      operationId: euaiactRecordAccuracyViaAgent
      parameters:
        - $ref: '#/components/parameters/LicenseKey'
      responses:
        '201':
          description: Metric recorded (see orchestrator-api.yaml for the schema)
  /api/v1/euaiact/accuracy/bias:
    post:
      tags:
        - EU AI Act (Proxied)
      summary: Record a bias measurement (proxied to orchestrator)
      description: Agent-proxied. Canonical contract in orchestrator-api.yaml.
      operationId: euaiactRecordBiasViaAgent
      parameters:
        - $ref: '#/components/parameters/LicenseKey'
      responses:
        '201':
          description: Bias measurement recorded (see orchestrator-api.yaml for the schema)
  /api/v1/euaiact/accuracy/history:
    get:
      tags:
        - EU AI Act (Proxied)
      summary: Accuracy metric history (proxied to orchestrator)
      description: Agent-proxied. Canonical contract in orchestrator-api.yaml.
      operationId: euaiactAccuracyHistoryViaAgent
      parameters:
        - $ref: '#/components/parameters/LicenseKey'
      responses:
        '200':
          description: Metric history (see orchestrator-api.yaml for the schema)
  /api/v1/euaiact/accuracy/alerts:
    get:
      tags:
        - EU AI Act (Proxied)
      summary: List accuracy alerts (proxied to orchestrator)
      description: Agent-proxied. Canonical contract in orchestrator-api.yaml.
      operationId: euaiactListAlertsViaAgent
      parameters:
        - $ref: '#/components/parameters/LicenseKey'
      responses:
        '200':
          description: Alert list (see orchestrator-api.yaml for the schema)
  /api/v1/circuit-breaker/status:
    get:
      tags:
        - Circuit Breaker
      summary: Get circuit breaker status
      description: |
        Returns all active (open) circuits for the organization.
        Uses X-Org-ID header for org identification.
      operationId: getCircuitBreakerStatus
      parameters:
        - $ref: '#/components/parameters/LicenseKey'
      responses:
        '200':
          description: Active circuit breaker circuits
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: object
                    properties:
                      active_circuits:
                        type: array
                        items:
                          $ref: '#/components/schemas/CircuitBreaker'
                      count:
                        type: integer
                      emergency_stop_active:
                        type: boolean
        '400':
          $ref: '#/components/responses/BadRequest'
  /api/v1/circuit-breaker/history:
    get:
      tags:
        - Circuit Breaker
      summary: Get circuit breaker history
      description: |
        Returns circuit breaker trip/reset history for audit trail.
        Ordered by creation time descending.
      operationId: getCircuitBreakerHistory
      parameters:
        - $ref: '#/components/parameters/LicenseKey'
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 50
      responses:
        '200':
          description: Circuit breaker history
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: object
                    properties:
                      history:
                        type: array
                        items:
                          $ref: '#/components/schemas/CircuitBreaker'
                      count:
                        type: integer
        '400':
          $ref: '#/components/responses/BadRequest'
  /api/v1/circuit-breaker/config:
    get:
      tags:
        - Circuit Breaker
      summary: Get circuit breaker config
      description: >
        Returns effective circuit breaker configuration. If tenant_id is
        provided,

        returns tenant-specific overrides merged with global defaults. Otherwise

        returns global defaults.
      operationId: getCircuitBreakerConfig
      parameters:
        - $ref: '#/components/parameters/LicenseKey'
        - name: tenant_id
          in: query
          schema:
            type: string
          description: Optional tenant ID for tenant-specific config
      responses:
        '200':
          description: Effective circuit breaker configuration
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    $ref: '#/components/schemas/CircuitBreakerConfigResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
    put:
      tags:
        - Circuit Breaker
      summary: Update per-tenant circuit breaker config
      description: |
        Creates or updates per-tenant circuit breaker threshold overrides.
        Null fields fall back to global defaults.
      operationId: updateCircuitBreakerConfig
      parameters:
        - $ref: '#/components/parameters/LicenseKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CircuitBreakerConfigUpdate'
      responses:
        '200':
          description: Config updated
        '400':
          $ref: '#/components/responses/BadRequest'
  /api/v1/circuit-breaker/notifications:
    get:
      tags:
        - Circuit Breaker
      summary: List notification configs
      description: Returns all circuit breaker notification configs for the organization.
      operationId: listCircuitBreakerNotifications
      parameters:
        - $ref: '#/components/parameters/LicenseKey'
      responses:
        '200':
          description: Notification configs
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: object
                    properties:
                      notifications:
                        type: array
                        items:
                          $ref: >-
                            #/components/schemas/CircuitBreakerNotificationConfig
                      count:
                        type: integer
        '400':
          description: Missing X-Org-ID header
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    post:
      tags:
        - Circuit Breaker
      summary: Create notification config
      description: >
        Create a notification channel for circuit breaker auto-trip events.

        Supports webhook (HMAC-signed), Slack (Block Kit), and PagerDuty (Events
        API v2).
      operationId: createCircuitBreakerNotification
      parameters:
        - $ref: '#/components/parameters/LicenseKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CircuitBreakerNotificationCreate'
      responses:
        '201':
          description: Notification config created
        '400':
          $ref: '#/components/responses/BadRequest'
  /api/v1/circuit-breaker/notifications/{id}:
    put:
      tags:
        - Circuit Breaker
      summary: Update notification config
      operationId: updateCircuitBreakerNotification
      parameters:
        - $ref: '#/components/parameters/LicenseKey'
        - name: id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CircuitBreakerNotificationUpdate'
      responses:
        '200':
          description: Notification config updated
        '404':
          description: Notification config not found
    delete:
      tags:
        - Circuit Breaker
      summary: Delete notification config
      operationId: deleteCircuitBreakerNotification
      parameters:
        - $ref: '#/components/parameters/LicenseKey'
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Notification config deleted
        '404':
          description: Notification config not found
  /api/v1/static-policies:
    get:
      tags:
        - Static Policies
      summary: List static policies
      description: >
        Returns static policies with three-tier hierarchy resolution:

        1. **System policies** - Managed by AxonFlow, immutable base

        2. **Organization policies** - Enterprise tier, organization-wide
        (Enterprise only)

        3. **Tenant policies** - Per-tenant customizations


        **v2.0.0 Categories** (semantic naming):

        - `security-sqli` - SQL injection detection (was: sql_injection)

        - `security-admin` - Admin access protection (was: admin_access)

        - `pii-global` - Global PII patterns (was: pii_detection)

        - `pii-us` - US-specific PII (SSN, etc.)

        - `pii-eu` - EU-specific PII (GDPR)

        - `pii-india` - India-specific PII (Aadhaar, PAN)

        - `custom` - Tenant-created policies


        Legacy category names are still accepted and automatically mapped.


        Part of ADR-018: Unified Policy Management System.
      operationId: listStaticPolicies
      parameters:
        - name: Authorization
          in: header
          required: false
          description: >-
            Basic auth credentials: Basic base64(clientId:clientSecret).
            Required in evaluation/enterprise mode. Optional in community mode
            (defaults to community tenant).
          schema:
            type: string
        - name: page
          in: query
          description: Page number (1-indexed)
          schema:
            type: integer
            default: 1
            minimum: 1
        - name: page_size
          in: query
          description: Number of policies per page
          schema:
            type: integer
            default: 20
            minimum: 1
            maximum: 100
        - name: category
          in: query
          description: >
            Filter by policy category. New categories use semantic naming.

            Legacy names (sql_injection, pii_detection, etc.) are still
            accepted.
          schema:
            type: string
            enum:
              - security-sqli
              - security-admin
              - pii-global
              - pii-us
              - pii-eu
              - pii-india
              - custom
              - sql_injection
              - pii_detection
              - dangerous_queries
              - admin_access
        - name: tier
          in: query
          description: Filter by policy tier
          schema:
            type: string
            enum:
              - system
              - organization
              - tenant
        - name: severity
          in: query
          description: Filter by severity level
          schema:
            type: string
            enum:
              - critical
              - high
              - medium
              - low
        - name: enabled
          in: query
          description: Filter by enabled status
          schema:
            type: boolean
      responses:
        '200':
          description: List of static policies
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StaticPoliciesListResponse'
        '400':
          description: Invalid request body or parameters
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    post:
      tags:
        - Static Policies
      summary: Create static policy
      description: |
        Creates a new static policy for the tenant.

        **Tier restrictions:**
        - `system` tier: Cannot be created via API (managed by AxonFlow)
        - `organization` tier: Requires Enterprise license
        - `tenant` tier: Default, limited to 30 policies in Community mode

        Part of ADR-018: Unified Policy Management System.
      operationId: createStaticPolicy
      parameters:
        - name: Authorization
          in: header
          required: false
          description: >-
            Basic auth credentials: Basic base64(clientId:clientSecret).
            Required in evaluation/enterprise mode. Optional in community mode
            (defaults to community tenant).
          schema:
            type: string
        - name: X-User-ID
          in: header
          required: false
          description: User ID for audit trail
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateStaticPolicyRequest'
            examples:
              customPII:
                summary: Create custom PII detection policy
                value:
                  name: Custom Employee ID Detection
                  description: Detects internal employee ID format
                  category: custom
                  tier: tenant
                  pattern: EMP-[0-9]{6}
                  action: warn
                  severity: medium
                  enabled: true
                  tags:
                    - custom
                    - pii
                    - employee
      responses:
        '201':
          description: Policy created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StaticPolicy'
        '400':
          description: Invalid request body or missing required fields
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: Operation forbidden (e.g., system tier creation, license required)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /api/v1/static-policies/effective:
    get:
      tags:
        - Static Policies
      summary: Get effective policies
      description: |
        Returns all effective policies for a tenant with overrides applied.

        This endpoint resolves the three-tier hierarchy:
        1. System policies (base)
        2. Organization overrides (if any)
        3. Tenant overrides (if any)

        Used by the Customer Portal for the unified policy view.
      operationId: getEffectivePolicies
      parameters:
        - name: Authorization
          in: header
          required: false
          description: >-
            Basic auth credentials: Basic base64(clientId:clientSecret).
            Required in evaluation/enterprise mode. Optional in community mode
            (defaults to community tenant).
          schema:
            type: string
      responses:
        '200':
          description: Effective policies with overrides applied
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EffectivePoliciesResponse'
        '400':
          description: Invalid request body or parameters
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /api/v1/static-policies/test:
    post:
      tags:
        - Static Policies
      summary: Test regex pattern
      description: |
        Tests a regex pattern against input strings without creating a policy.

        Useful for validating patterns before creating policies.
        Has a 5-second timeout to prevent ReDoS attacks.
      operationId: testStaticPolicyPattern
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TestPatternRequest'
            examples:
              singleInput:
                summary: Test against single input
                value:
                  pattern: \b[0-9]{3}-[0-9]{2}-[0-9]{4}\b
                  input: My SSN is 123-45-6789
              multipleInputs:
                summary: Test against multiple inputs
                value:
                  pattern: \b[0-9]{3}-[0-9]{2}-[0-9]{4}\b
                  inputs:
                    - My SSN is 123-45-6789
                    - No SSN here
                    - 'Another SSN: 987-65-4321'
      responses:
        '200':
          description: Pattern test results
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TestPatternResponse'
        '400':
          description: Invalid request body or pattern
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /api/v1/static-policies/overrides:
    get:
      tags:
        - Static Policies
      summary: List policy overrides
      description: |
        Lists all policy overrides for a tenant.

        Overrides allow Enterprise customers to modify system policy behavior
        (action, enabled status) without changing the underlying pattern.
      operationId: listStaticPolicyOverrides
      parameters:
        - name: Authorization
          in: header
          required: false
          description: >-
            Basic auth credentials: Basic base64(clientId:clientSecret).
            Required in evaluation/enterprise mode. Optional in community mode
            (defaults to community tenant).
          schema:
            type: string
        - name: include_expired
          in: query
          required: false
          description: Include expired overrides in results
          schema:
            type: boolean
            default: false
      responses:
        '200':
          description: List of policy overrides
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PolicyOverridesListResponse'
        '400':
          description: Invalid request body or parameters
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /api/v1/policy-overrides:
    get:
      tags:
        - Static Policies
      summary: List policy overrides (canonical alias)
      description: |
        Portal-facing alias of `GET /api/v1/static-policies/overrides` —
        identical handler, parameters, and response.
      operationId: listPolicyOverrides
      parameters:
        - $ref: '#/components/parameters/LicenseKey'
        - name: include_expired
          in: query
          required: false
          description: Include expired overrides in results
          schema:
            type: boolean
            default: false
      responses:
        '200':
          description: List of policy overrides
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PolicyOverridesListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /api/v1/static-policies/{id}:
    get:
      tags:
        - Static Policies
      summary: Get static policy by ID
      description: >
        Returns a single static policy by its UUID.


        The policy ID is the `id` field from the static_policies table,

        not the `policy_id` (human-readable identifier like
        'sql_injection_union').
      operationId: getStaticPolicy
      parameters:
        - name: Authorization
          in: header
          required: false
          description: >-
            Basic auth credentials: Basic base64(clientId:clientSecret).
            Required in evaluation/enterprise mode. Optional in community mode
            (defaults to community tenant).
          schema:
            type: string
        - name: id
          in: path
          required: true
          description: Static policy UUID
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Static policy details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StaticPolicy'
        '400':
          description: Invalid request body or parameters
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Policy not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    put:
      tags:
        - Static Policies
      summary: Update static policy
      description: |
        Updates an existing static policy.

        **Restrictions:**
        - System-tier policies cannot be modified (use overrides instead)
        - Pattern changes trigger version increment
      operationId: updateStaticPolicy
      parameters:
        - name: id
          in: path
          required: true
          description: Static policy UUID
          schema:
            type: string
            format: uuid
        - name: X-User-ID
          in: header
          required: false
          description: User ID for audit trail
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateStaticPolicyRequest'
      responses:
        '200':
          description: Policy updated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StaticPolicy'
        '400':
          description: Invalid request body
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: System policies cannot be modified
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Policy not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    delete:
      tags:
        - Static Policies
      summary: Delete static policy
      description: |
        Soft-deletes a static policy (sets deleted_at timestamp).

        **Restrictions:**
        - System-tier policies cannot be deleted
      operationId: deleteStaticPolicy
      parameters:
        - name: id
          in: path
          required: true
          description: Static policy UUID
          schema:
            type: string
            format: uuid
        - name: X-User-ID
          in: header
          required: false
          description: User ID for audit trail
          schema:
            type: string
      responses:
        '204':
          description: Policy deleted successfully
        '403':
          description: System policies cannot be deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Policy not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    patch:
      tags:
        - Static Policies
      summary: Toggle policy enabled status
      description: >
        Toggles the enabled status of a policy.


        **Restrictions:**

        - System-tier policies cannot be disabled via this endpoint (use
        overrides)
      operationId: toggleStaticPolicy
      parameters:
        - name: id
          in: path
          required: true
          description: Static policy UUID
          schema:
            type: string
            format: uuid
        - name: X-User-ID
          in: header
          required: false
          description: User ID for audit trail
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - enabled
              properties:
                enabled:
                  type: boolean
                  description: New enabled status
      responses:
        '200':
          description: Policy updated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StaticPolicy'
        '403':
          description: System policies cannot be disabled
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Policy not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /api/v1/static-policies/{id}/versions:
    get:
      tags:
        - Static Policies
      summary: Get policy version history
      description: |
        Returns the version history for a policy.

        **Edition limits:**
        - Community: Last 5 versions
        - Enterprise: Unlimited history
      operationId: getStaticPolicyVersions
      parameters:
        - name: id
          in: path
          required: true
          description: Static policy UUID
          schema:
            type: string
            format: uuid
        - name: Authorization
          in: header
          required: false
          description: >-
            Basic auth credentials: Basic base64(clientId:clientSecret).
            Required in evaluation/enterprise mode. Optional in community mode
            (defaults to community tenant).
          schema:
            type: string
      responses:
        '200':
          description: Version history
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PolicyVersionsResponse'
        '404':
          description: Policy not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /api/v1/static-policies/{id}/override:
    get:
      tags:
        - Static Policies
      summary: Get active override for a policy
      description: |
        Returns the active override for a single policy (404 when none).
        `{id}` may be the policy UUID or the human-readable slug — slugs
        are resolved to the canonical UUID before lookup.
      operationId: getStaticPolicyOverride
      parameters:
        - $ref: '#/components/parameters/LicenseKey'
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: The active override
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PolicyOverride'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          description: No override exists for this policy
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JSONError'
    post:
      tags:
        - Static Policies
      summary: Create policy override
      description: |
        Creates an override for a system policy.

        **Enterprise only.** Overrides allow modifying policy behavior
        (action, enabled status) without changing the underlying pattern.

        Overrides can be scoped to organization or tenant level and
        can have an optional expiration date.
      operationId: createStaticPolicyOverride
      parameters:
        - name: id
          in: path
          required: true
          description: Static policy UUID to override
          schema:
            type: string
            format: uuid
        - name: Authorization
          in: header
          required: false
          description: >-
            Basic auth credentials: Basic base64(clientId:clientSecret).
            Required in evaluation/enterprise mode. Optional in community mode
            (defaults to community tenant).
          schema:
            type: string
        - name: X-User-ID
          in: header
          required: false
          description: User ID for audit trail
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateOverrideRequest'
            examples:
              disablePolicy:
                summary: Disable a system policy
                value:
                  enabled_override: false
                  override_reason: False positive rate too high for this tenant
                  expires_at: '2025-06-01T00:00:00Z'
              changeAction:
                summary: Change action from block to warn
                value:
                  action_override: warn
                  override_reason: Regulatory requirement to warn instead of block
      responses:
        '201':
          description: |
            Override created successfully. Returns `CreateOverrideResponse`
            with the effective `ttl_seconds` (post server-side clamping)
            and, when applicable, the `requested_ttl` / `clamped` /
            `clamped_reason` metadata so callers can surface clamping
            decisions in their UX. The at-rest `PolicyOverride` entity
            is returned by `GET /api/v1/policy-overrides` /
            `GET /api/v1/policy-overrides/{id}`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreateOverrideResponse'
        '400':
          description: Invalid request body or missing required fields
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: Enterprise license required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Policy not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    delete:
      tags:
        - Static Policies
      summary: Delete policy override
      description: |
        Deletes an override, restoring the original policy behavior.

        **Enterprise only.**
      operationId: deleteStaticPolicyOverride
      parameters:
        - name: id
          in: path
          required: true
          description: Static policy UUID
          schema:
            type: string
            format: uuid
        - name: Authorization
          in: header
          required: false
          description: >-
            Basic auth credentials: Basic base64(clientId:clientSecret).
            Required in evaluation/enterprise mode. Optional in community mode
            (defaults to community tenant).
          schema:
            type: string
        - name: X-User-ID
          in: header
          required: false
          description: User ID for audit trail
          schema:
            type: string
      responses:
        '204':
          description: Override deleted successfully
        '403':
          description: Enterprise license required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Override not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /api/v1/audit/chains/{chainID}/verify:
    get:
      tags:
        - Audit Verification
      summary: Verify a decision chain
      description: |
        Re-verifies an entire decision chain: the `prev_hash` linkage across all
        of its records (ordering / completeness) and every per-record Ed25519
        signature (authorship). Read-only; never mutates the chain.

        The chain is resolved by id **only within the authenticated caller's
        organization** (RLS-scoped); the org is taken from the request
        credentials, never from a path or query parameter. `chainID` is the
        decision id (a UUID).

        Inspect `authorship_proven` for the strong non-repudiation claim (every
        record signed and all signatures + linkage verify). `valid` only means
        no integrity violation was detected and can be `true` for a chain with
        zero signed records, so do not gate non-repudiation on `valid` alone.
      operationId: verifyAuditChain
      parameters:
        - $ref: '#/components/parameters/LicenseKey'
        - name: chainID
          in: path
          required: true
          description: Decision chain id (the decision UUID)
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Verification result (the chain exists in your organization)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ChainVerificationResult'
        '400':
          description: chainID is not a valid UUID
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuditVerifyError'
        '401':
          description: Missing authenticated org context
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuditVerifyError'
        '404':
          description: No decision chain found for this id in your organization
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuditVerifyError'
        '500':
          description: Verification failed (internal error)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuditVerifyError'
  /api/v1/audit/records/{recordID}/verify:
    get:
      tags:
        - Audit Verification
      summary: Verify a single decision record
      description: >
        Verifies ONE decision record standalone, proving its authorship from the

        record alone without walking the rest of the chain. Read-only.


        The response republishes the recomputed verification material so the

        result is independently checkable offline: `digest_preimage_b64` is the

        exact byte string that SHA-256-hashes to `record_digest`; an auditor can

        base64-decode it, hash it, confirm it equals `record_digest`, rebuild it

        from the raw record fields, and then run

        `ed25519.Verify(public_key, []byte(chain_hash),
        base64decode(record_signature))`,

        trusting neither this endpoint nor its digest.


        The record is resolved by id only within the authenticated caller's

        organization (RLS-scoped). `recordID` is the per-record UUID assigned at

        signing time (distinct from the chain id).
      operationId: verifyAuditRecord
      parameters:
        - $ref: '#/components/parameters/LicenseKey'
        - name: recordID
          in: path
          required: true
          description: Decision record id (the per-record UUID)
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Verification result (the record exists in your organization)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RecordVerificationResult'
        '400':
          description: recordID is not a valid UUID
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuditVerifyError'
        '401':
          description: Missing authenticated org context
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuditVerifyError'
        '404':
          description: No decision record found for this id in your organization
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuditVerifyError'
        '500':
          description: Verification failed (internal error)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuditVerifyError'
  /api/v1/audit/signing-key:
    get:
      tags:
        - Audit Verification
      summary: Publish the current public signing key
      description: |
        Returns the current public Ed25519 verification key so an external
        auditor can re-verify any record's signature offline. Read-only.

        When no signing key is configured, `configured` is `false` and
        `public_key` is empty: records are hash-chained but unsigned, and the
        verify endpoints report that honestly.
      operationId: getAuditSigningKey
      parameters:
        - $ref: '#/components/parameters/LicenseKey'
      responses:
        '200':
          description: Current public signing key (or that none is configured)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuditSigningKeyResponse'
        '401':
          description: Missing authenticated org context
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuditVerifyError'
components:
  securitySchemes:
    BasicAuth:
      type: http
      scheme: basic
      description: >
        OAuth2-style Basic authentication using `clientId:clientSecret`
        credentials.


        **Header format:** `Authorization: Basic base64(clientId:clientSecret)`


        - `clientId` (required): Your organization/client identifier

        - `clientSecret` (optional): Authentication credential. Optional for
        community/self-hosted mode.


        **Example:**

        ```bash

        # With clientSecret (enterprise)

        curl -H "Authorization: Basic $(echo -n 'my-org:AXON-V2-xxx' | base64)"
        ...


        # Without clientSecret (community mode)

        curl -H "Authorization: Basic $(echo -n 'my-org:' | base64)" ...

        ```
    InternalServiceID:
      type: apiKey
      in: header
      name: X-Internal-Service-ID
      description: |
        Internal-service (operator lane) credential — **part one of two**.
        Must be sent together with `X-Internal-Service-Token`; either header
        alone is not a credential.

        This is the HMAC identity the Orchestrator and the Enterprise
        customer-portal use to call agent endpoints without holding a
        customer license. `apiAuthMiddleware` lifts both headers (plus an
        optional `X-Tenant-ID` scope) into `AuthHints`
        (`platform/agent/auth.go:588-594`) and `Authenticate()` validates
        them before any mode-specific auth
        (`platform/agent/authenticator.go:120-155`).

        Value: the service id, `orchestrator-internal`.

        ⚠️ An invalid or expired token is **not** an error by itself — it
        falls through to the deployment's normal auth
        (`platform/agent/authenticator.go:153-154`). Send the internal-service
        headers on their own: paired with an `Authorization: Basic` header, a
        stale token silently yields a *tenant*-scoped answer that looks like a
        successful operator call.
    InternalServiceToken:
      type: apiKey
      in: header
      name: X-Internal-Service-Token
      description: |
        Internal-service (operator lane) credential — **part two of two**.
        Must be sent together with `X-Internal-Service-ID`.

        Format: `AXON-INTERNAL-{unix_ts}-{sig}`, where `sig` is the first 16
        hex characters of HMAC-SHA256 over `orchestrator-internal:{unix_ts}`
        keyed with `AXONFLOW_INTERNAL_SERVICE_SECRET`. Validated by
        `platform/shared/serviceauth` within a 5-minute clock-skew window, so
        it must be re-minted per session. See
        `technical-docs/runbooks/RUNBOOK_CONNECTOR_CONFIGURATION.md` for the
        exact minting snippet.
  parameters:
    LicenseKey:
      name: Authorization
      in: header
      required: true
      description: >
        OAuth2-style Basic authentication header.

        Format: `Basic base64(clientId:clientSecret)`


        - `clientId`: Your organization identifier (required)

        - `clientSecret`: Authentication credential (optional for community
        mode)


        Not required when `DEPLOYMENT_MODE=community`.
      schema:
        type: string
        example: Basic bXktb3JnOkFYT04tVjIteHh4
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: false
      description: >
        Optional per-request dedup token. When supplied on POST
        /api/v1/mcp/check-input,

        POST /api/v1/audit/tool-call, or POST /api/v1/hitl/queue, the platform
        caches

        the original response for 24h and returns it byte-for-byte on subsequent

        requests carrying the same key + same authenticated tenant + same
        endpoint.


        Format: 1-256 chars, `^[A-Za-z0-9_.:\-/]+$`. Workflow IDs from n8n, ADK,

        or generic SDKs all fall inside this set. A malformed key returns 400

        before the handler runs.


        Cache rules: 2xx + 4xx responses are cached; 5xx is NOT cached so the

        caller's retry can hit a fresh attempt. A cache hit returns the original

        response plus an `Idempotent-Replayed: true` response header.


        Cross-tenant collisions are impossible: tenant_id participates in the

        primary key + an RLS policy on the storage table. Two tenants using

        the same key value get distinct rows.
      schema:
        type: string
        minLength: 1
        maxLength: 256
        pattern: ^[A-Za-z0-9_.:\-/]+$
        example: n8n-exec-abc123-node-Approve
    AxonflowClient:
      name: X-Axonflow-Client
      in: header
      required: false
      description: |
        Optional client-version telemetry header (`<client>/<version>`,
        e.g. `mcp-proxy/0.3.1` or `claude-code/1.9.1`). Enterprise
        deployments with the `client_version_telemetry` capability count
        validated values in the `axonflow_client_version_requests_total`
        metric on the decide and MCP check-output planes. Telemetry only —
        never used for authentication or authorization; invalid values are
        ignored.
      schema:
        type: string
        example: mcp-proxy/0.3.1
    ClientSecret:
      name: X-Client-Secret
      in: header
      required: false
      deprecated: true
      description: |
        **DEPRECATED**: Use Basic authentication instead.
        Legacy client secret header. Prefer using `Authorization: Basic` header.
      schema:
        type: string
  schemas:
    HealthResponse:
      type: object
      properties:
        status:
          type: string
          enum:
            - healthy
            - starting
          description: Service health status (the handler emits only these two)
        service:
          type: string
          example: axonflow-agent
        timestamp:
          type: string
          format: date-time
        version:
          type: string
          description: Platform version from AXONFLOW_VERSION env var
          example: 9.8.0
        tier:
          type: string
          description: >-
            License tier of the deployment (e.g. community, evaluation,
            enterprise)
        capabilities:
          type: array
          description: List of platform capabilities with the version that introduced them
          items:
            $ref: '#/components/schemas/PlatformCapability'
        sdk_compatibility:
          $ref: '#/components/schemas/SDKCompatInfo'
        plugin_compatibility:
          $ref: '#/components/schemas/PluginCompatInfo'
    PlatformCapability:
      type: object
      properties:
        name:
          type: string
          description: Capability identifier
          example: workflow_control
        since:
          type: string
          description: Platform version that introduced this capability
          example: 4.3.0
        description:
          type: string
          description: Human-readable description
          example: WCP workflow lifecycle management
    SDKCompatInfo:
      type: object
      description: |
        SDK version compatibility information. Each key in the maps below
        is one of `python` / `typescript` / `go` / `java`. SDKs that find
        their own runtime version below `min_sdk_version[<their lang>]`
        log a one-time upgrade warning; below `recommended_sdk_version`
        is informational only.
      properties:
        min_sdk_version:
          type: object
          additionalProperties:
            type: string
          description: |
            Per-language minimum SDK version compatible with this
            platform. Pre-v4.8.0 platforms returned a bare string; SDKs
            v6.0.0+ accept either shape.
          example:
            python: 6.0.0
            typescript: 5.0.0
            go: 5.0.0
            java: 5.0.0
        recommended_sdk_version:
          type: object
          additionalProperties:
            type: string
          description: Per-language SDK version this platform was tested against.
          example:
            python: 6.9.0
            typescript: 6.2.0
            go: 6.0.0
            java: 6.2.0
    PluginCompatInfo:
      type: object
      description: |
        Plugin version compatibility information. Mirrors `SDKCompatInfo`
        for the four AxonFlow plugins (`openclaw`, `claude-code`,
        `cursor`, `codex`). Plugins read this from `/health` and warn
        when their runtime version is below `min_plugin_version[<their id>]`.
        Keys match the canonical plugin IDs the agent tracks in
        `integration_activation.go::knownIntegrations`.
      properties:
        min_plugin_version:
          type: object
          additionalProperties:
            type: string
          description: Per-plugin minimum version compatible with this platform.
          example:
            openclaw: 2.0.0
            claude-code: 1.0.0
            cursor: 1.0.0
            codex: 1.0.0
        recommended_plugin_version:
          type: object
          additionalProperties:
            type: string
          description: Per-plugin version this platform was tested against.
          example:
            openclaw: 2.0.0
            claude-code: 1.0.0
            cursor: 1.0.0
            codex: 1.0.0
    StaticPolicy:
      type: object
      description: >
        A static policy with three-tier hierarchy support (v2.0.0).

        System policies are immutable; Organization/Tenant policies can be
        customized.
      properties:
        id:
          type: string
          format: uuid
          description: Unique policy UUID
        policy_id:
          type: string
          description: Human-readable policy identifier (e.g., sys_sqli_union_select)
          example: sys_sqli_union_select
        name:
          type: string
          description: Display name of the policy
          example: UNION SELECT Detection
        description:
          type: string
          description: Detailed policy description
          example: Detects SQL injection attempts using UNION SELECT
        category:
          type: string
          enum:
            - security-sqli
            - security-admin
            - pii-global
            - pii-us
            - pii-eu
            - pii-india
            - custom
          description: Policy category (v2.0.0 semantic naming)
        tier:
          type: string
          enum:
            - system
            - organization
            - tenant
          description: Policy tier in the hierarchy
          example: system
        pattern:
          type: string
          description: Regex pattern for detection
          example: (?i)\bUNION\s+(ALL\s+)?SELECT\b
        severity:
          type: string
          enum:
            - critical
            - high
            - medium
            - low
          description: Policy severity level
        action:
          type: string
          enum:
            - block
            - require_approval
            - redact
            - warn
            - log
          description: >-
            Action to take when pattern matches. require_approval triggers HITL
            queue for human oversight.
        enabled:
          type: boolean
          description: Whether policy is active
        priority:
          type: integer
          description: Evaluation priority (higher = evaluated first)
          example: 1000
        version:
          type: integer
          description: Policy version number
          example: 1
        has_override:
          type: boolean
          description: Whether this policy has an override (Enterprise only)
        override:
          $ref: '#/components/schemas/PolicyOverride'
        organization_id:
          type: string
          description: Organization ID (for organization-tier policies)
        tenant_id:
          type: string
          description: Tenant ID (for tenant-tier policies)
        created_at:
          type: string
          format: date-time
          description: When policy was created
        updated_at:
          type: string
          format: date-time
          description: When policy was last modified
      required:
        - id
        - policy_id
        - name
        - category
        - pattern
        - severity
        - action
        - enabled
        - tenant_id
        - created_at
        - updated_at
    StaticPoliciesListResponse:
      type: object
      properties:
        policies:
          type: array
          items:
            $ref: '#/components/schemas/StaticPolicy'
          description: List of static policies
        pagination:
          $ref: '#/components/schemas/StaticPolicyPagination'
    StaticPolicyPagination:
      type: object
      properties:
        page:
          type: integer
          description: Current page number (1-indexed)
        page_size:
          type: integer
          description: Number of items per page
        total:
          type: integer
          description: Total number of policies
        total_pages:
          type: integer
          description: Total number of pages
    PolicyOverride:
      type: object
      description: Override configuration for system policies (Enterprise only)
      properties:
        id:
          type: string
          description: Override identifier
        policy_id:
          type: string
          description: ID of the policy being overridden
        policy_type:
          type: string
          enum:
            - static
            - dynamic
          description: Type of policy being overridden
        organization_id:
          type: string
          description: Organization ID for org-level overrides
        tenant_id:
          type: string
          description: Tenant ID for tenant-level overrides
        enabled_override:
          type: boolean
          description: Override enabled status (null = no override)
        action_override:
          type: string
          enum:
            - block
            - require_approval
            - redact
            - warn
            - log
          description: >-
            Override action (must be more restrictive). require_approval
            triggers HITL queue.
        override_reason:
          type: string
          description: Reason for the override (required for audit)
        expires_at:
          type: string
          format: date-time
          description: When the override automatically expires
        created_by:
          type: string
          description: User who created the override
        created_at:
          type: string
          format: date-time
        updated_by:
          type: string
          description: User who last updated the override
        updated_at:
          type: string
          format: date-time
    CreateStaticPolicyRequest:
      type: object
      description: Request body for creating a static policy
      required:
        - name
        - pattern
        - category
        - action
      properties:
        name:
          type: string
          description: Display name of the policy
          example: Custom Employee ID Detection
        description:
          type: string
          description: Detailed policy description
        category:
          type: string
          enum:
            - security-sqli
            - security-admin
            - pii-global
            - pii-us
            - pii-eu
            - pii-india
            - custom
          description: Policy category
        tier:
          type: string
          enum:
            - organization
            - tenant
          description: Policy tier (system not allowed via API). Default is tenant.
          default: tenant
        pattern:
          type: string
          description: Regex pattern for detection
          example: EMP-[0-9]{6}
        action:
          type: string
          enum:
            - block
            - require_approval
            - redact
            - warn
            - log
          description: Action to take when pattern matches
        severity:
          type: string
          enum:
            - critical
            - high
            - medium
            - low
          description: Severity level
        priority:
          type: integer
          description: Priority order (lower = higher priority)
          default: 100
        enabled:
          type: boolean
          description: Whether the policy is active
          default: true
        tags:
          type: array
          items:
            type: string
          description: Tags for categorization
        organization_id:
          type: string
          description: |
            Organization id for organization-tier policies (Enterprise).
            Required when `tier: organization`; ignored otherwise.
    UpdateStaticPolicyRequest:
      type: object
      description: Request body for updating a static policy (all fields optional)
      properties:
        name:
          type: string
          description: Display name of the policy
        description:
          type: string
          description: Detailed policy description
        pattern:
          type: string
          description: Regex pattern (only for non-system policies)
        action:
          type: string
          enum:
            - block
            - require_approval
            - redact
            - warn
            - log
        severity:
          type: string
          enum:
            - critical
            - high
            - medium
            - low
        priority:
          type: integer
        enabled:
          type: boolean
        category:
          type: string
          description: |
            Policy category. Updates can re-categorise non-system
            policies; system policies stay pinned to their seeded
            category.
          enum:
            - security-sqli
            - security-admin
            - pii-global
            - pii-us
            - pii-eu
            - pii-india
            - custom
        tags:
          type: array
          items:
            type: string
    CreateOverrideRequest:
      type: object
      description: Request body for creating a policy override
      required:
        - override_reason
      properties:
        action_override:
          type: string
          enum:
            - block
            - require_approval
            - redact
            - warn
            - log
          description: Override the policy action
        enabled_override:
          type: boolean
          description: Override the enabled status
        override_reason:
          type: string
          description: Required explanation for audit trail
        expires_at:
          type: string
          format: date-time
          description: Optional expiration for the override
    CreateOverrideResponse:
      type: object
      description: |
        Response returned by `POST /api/v1/policies/{id}/overrides` on
        successful create. Carries the create-time-only TTL clamping
        metadata (`requested_ttl`, `clamped`, `clamped_reason`) that
        is not present on the at-rest `PolicyOverride` entity returned
        by `GET /api/v1/policy-overrides` / `GET /api/v1/policy-overrides/{id}`.
        Source of truth: `platform/orchestrator/overrides_handler.go`
        (`CreateOverrideResponse`). Mirrors the `CreateWorkflowResponse`
        precedent (orchestrator-api.yaml) — create-time concerns split
        from at-rest concerns rather than overloaded onto one schema.
      required:
        - id
        - policy_id
        - policy_type
        - expires_at
        - ttl_seconds
        - created_at
      properties:
        id:
          type: string
          description: Unique override identifier
        policy_id:
          type: string
          description: ID of the policy being overridden
        policy_type:
          type: string
          enum:
            - static
            - dynamic
          description: Type of policy being overridden
        expires_at:
          type: string
          format: date-time
          description: Override expiration timestamp (clamped server-side per ADR-044)
        ttl_seconds:
          type: integer
          format: int64
          description: |
            Effective time-to-live in seconds, after server-side clamping.
            Default 60m, hard cap 24h, minimum 60s.
        requested_ttl:
          type: integer
          format: int64
          description: |
            The TTL the caller originally requested, included only when it
            differs from `ttl_seconds` (i.e. when `clamped: true`). Lets
            callers detect and surface clamping in their UX.
        clamped:
          type: boolean
          description: |
            `true` if the requested TTL was adjusted by server-side
            clamping. Omitted when no clamping occurred.
        clamped_reason:
          type: string
          enum:
            - exceeds_hard_cap
            - below_minimum
          description: |
            Why the TTL was clamped. Present only when `clamped: true`.
        created_at:
          type: string
          format: date-time
          description: When the override was created.
    TestPatternRequest:
      type: object
      description: Request body for testing a regex pattern
      required:
        - pattern
      properties:
        pattern:
          type: string
          description: Regex pattern to test
        input:
          type: string
          description: Single input string to test (for backward compatibility)
        inputs:
          type: array
          items:
            type: string
          description: Multiple input strings to test
    TestPatternResponse:
      type: object
      description: Response from pattern testing
      properties:
        valid:
          type: boolean
          description: Whether the pattern is valid regex
        results:
          type: array
          items:
            type: object
            properties:
              input:
                type: string
                description: The input that was tested
              matched:
                type: boolean
                description: Whether the pattern matched
              matches:
                type: array
                items:
                  type: string
                description: Captured match groups
        error:
          type: string
          description: Error message if pattern is invalid
    EffectivePoliciesResponse:
      type: object
      description: Effective policies with overrides resolved
      properties:
        static:
          type: array
          items:
            $ref: '#/components/schemas/StaticPolicy'
          description: Static policies with overrides applied
        tenant_id:
          type: string
        organization_id:
          type: string
        computed_at:
          type: string
          format: date-time
          description: When the effective policies were computed
    PolicyVersionsResponse:
      type: object
      description: Version history for a policy
      properties:
        policy_id:
          type: string
          description: Policy UUID
        versions:
          type: array
          items:
            $ref: '#/components/schemas/PolicyVersion'
        count:
          type: integer
          description: Number of versions returned
    PolicyVersion:
      type: object
      description: A version snapshot of a policy
      properties:
        id:
          type: string
          format: uuid
        policy_id:
          type: string
        version:
          type: integer
        snapshot:
          type: object
          description: Complete policy state at this version
        change_type:
          type: string
          enum:
            - created
            - updated
            - deleted
            - enabled
            - disabled
        change_summary:
          type: string
        changed_by:
          type: string
        changed_at:
          type: string
          format: date-time
    PolicyOverridesListResponse:
      type: object
      description: List of policy overrides
      properties:
        overrides:
          type: array
          items:
            $ref: '#/components/schemas/PolicyOverride'
        count:
          type: integer
          description: Total number of overrides
    MetricsResponse:
      type: object
      properties:
        agent_metrics:
          type: object
          properties:
            uptime_seconds:
              type: number
            total_requests:
              type: integer
            success_requests:
              type: integer
            failed_requests:
              type: integer
            blocked_requests:
              type: integer
            success_rate:
              type: number
              description: Success rate percentage
            rps:
              type: number
              description: Requests per second
            error_rate_per_sec:
              type: number
            p50_ms:
              type: number
              description: 50th percentile latency
            p95_ms:
              type: number
              description: 95th percentile latency
            p99_ms:
              type: number
              description: 99th percentile latency
            avg_latency_ms:
              type: number
            auth_p99_ms:
              type: number
              description: Authentication stage P99 latency
            static_policy_eval_p99_ms:
              type: number
              description: Static policy evaluation P99 latency
            network_p99_ms:
              type: number
              description: Network (Agent to Orchestrator) P99 latency
        health:
          type: object
          properties:
            status:
              type: string
              enum:
                - healthy
                - degraded
                - unhealthy
            healthy:
              type: boolean
            consecutive_errors:
              type: integer
            up:
              type: integer
              description: Always 1 if service is responding
        request_types:
          type: object
          additionalProperties:
            type: object
            properties:
              total_requests:
                type: integer
              success_requests:
                type: integer
              p99_ms:
                type: number
        connectors:
          type: object
          additionalProperties:
            type: object
            properties:
              total_requests:
                type: integer
              success_rate:
                type: number
              p99_ms:
                type: number
        timestamp:
          type: string
          format: date-time
    ClientRequest:
      type: object
      required:
        - query
        - client_id
      properties:
        query:
          type: string
          description: The query or prompt to process
          minLength: 1
          maxLength: 100000
        user_token:
          type: string
          description: JWT token for user authentication
        client_id:
          type: string
          description: Registered client application ID
        request_type:
          type: string
          enum:
            - sql
            - llm_chat
            - rag_search
            - mcp-query
            - multi-agent-plan
          description: |
            Type of request:
            - `sql`: Database query
            - `llm_chat`: LLM conversation
            - `rag_search`: RAG retrieval
            - `mcp-query`: MCP connector query
            - `multi-agent-plan`: Multi-agent planning
        skip_llm:
          type: boolean
          default: false
          description: Skip LLM calls (for testing)
        context:
          type: object
          additionalProperties: true
          description: Additional context for request processing
        media:
          type: array
          description: |
            Optional multimodal payload accompanying the query
            (images, documents, etc.). Consumed by the platform's
            media-governance code path; per-item shape is
            `MediaContent` from the SDK.
          items:
            type: object
            additionalProperties: true
    ClientResponse:
      type: object
      properties:
        success:
          type: boolean
        data:
          description: Response data (varies by request type)
        result:
          type: string
          description: Result string for multi-agent planning
        plan_id:
          type: string
          description: Plan ID for multi-agent planning
        metadata:
          type: object
          additionalProperties: true
          description: Execution metadata for multi-agent planning
        error:
          type: string
          description: Error message if success is false
        blocked:
          type: boolean
          description: True if request was blocked by policy
        block_reason:
          type: string
          description: Reason for blocking
        policy_info:
          $ref: '#/components/schemas/PolicyEvaluationInfo'
        budget_info:
          type: object
          description: |
            Budget enforcement status (Issue #1082) — present when a
            budget check ran. Surfaces current usage vs limits so
            callers can render budget-aware UI without a separate
            /api/v1/budgets call.
          additionalProperties: true
        media_analysis:
          type: object
          description: |
            Media-governance analysis result — populated when the
            request carried a `media` payload. Shape mirrors
            `MediaAnalysisResponse`.
          additionalProperties: true
    PolicyEvaluationInfo:
      type: object
      properties:
        policies_evaluated:
          type: array
          items:
            type: string
          description: List of policies that were evaluated
        static_checks:
          type: array
          items:
            type: string
          description: List of static checks performed
        processing_time:
          type: string
          description: Time taken for policy evaluation
          example: 2.5ms
        tenant_id:
          type: string
          description: Tenant ID for the request
        code_artifact:
          type: object
          description: |
            Code-artifact metadata captured by the code-governance
            evaluation path when the request carried code content
            (LLM-generated or user-provided). Mirrors the SDK's
            `CodeArtifact` type — language, code_type, size_bytes,
            line_count, secrets_detected, unsafe_patterns,
            policies_checked.
          additionalProperties: true
    PreCheckRequest:
      type: object
      required:
        - query
        - client_id
      properties:
        query:
          type: string
          description: Query to validate
          minLength: 1
        user_token:
          type: string
          description: JWT token for user authentication
        client_id:
          type: string
          description: Client application ID
        data_sources:
          type: array
          items:
            type: string
          description: MCP connectors to fetch data from
        context:
          type: object
          additionalProperties: true
          description: Additional context
    PreCheckResponse:
      type: object
      properties:
        context_id:
          type: string
          description: Context ID to use in audit call (valid for 5 minutes)
        approved:
          type: boolean
          description: Whether the request is approved
        approved_data:
          type: object
          additionalProperties: true
          description: Data fetched from MCP connectors
        policies:
          type: array
          items:
            type: string
          description: Policies that were evaluated
        rate_limit:
          $ref: '#/components/schemas/RateLimitInfo'
        expires_at:
          type: string
          format: date-time
          description: When the context expires
        block_reason:
          type: string
          description: Reason if request was blocked
        trace_id:
          type: string
          description: |
            W3C OpenTelemetry trace_id (32-char lowercase hex) emitted
            by the decision tracer. Optional: present when the tracer
            is enabled via AXONFLOW_OTEL_ENDPOINT, omitted otherwise.
            Policy Enforcement Points propagate this id downstream so
            multi-gateway decisions stitch into one end-to-end trace.
          example: b3a1f1f3a8c6e0d791bc3e7a8c2d5f4a
    RateLimitInfo:
      type: object
      properties:
        limit:
          type: integer
          description: Rate limit per window
        remaining:
          type: integer
          description: Remaining requests in current window
        reset_at:
          type: string
          format: date-time
          description: When the rate limit resets
    DecideRequest:
      type: object
      required:
        - stage
        - query
      properties:
        stage:
          type: string
          enum:
            - llm
            - tool
            - agent
          description: |
            Which gateway layer is calling. Maps to ADR-056's three-layer
            reference architecture (agent / MCP / LLM).
        caller_identity:
          $ref: '#/components/schemas/DecisionCallerIdentity'
        target:
          $ref: '#/components/schemas/DecisionTarget'
        query:
          type: string
          minLength: 1
          description: The request body / prompt / statement being decided on.
        user_token:
          type: string
          description: |
            Optional end-user JWT for audit identity. PEP gateways are
            typically services and may omit this field -- in enterprise
            mode the platform synthesizes a service identity for the
            audit row when no token is supplied. Supplying a token gets
            the validated-user record on the audit row instead.
        context:
          type: object
          additionalProperties: true
          description: |
            Optional caller-supplied context (string values) that AxonFlow
            propagates end-to-end into the decision audit record + the OTel
            decision span, so a SIEM can correlate the decision with upstream
            logs (e.g. by session_id). Intended for infrastructure-gateway
            audit headers such as `X-AI-Agent`, `X-Session-ID`,
            `X-Leader-Identity`, and a tenant-scoped header family.

            Only keys matching the server's allowlist
            (`AXONFLOW_DECISION_CONTEXT_ALLOWLIST`; the default covers common
            agent / session / leader identity headers plus a tenant-scoped
            header family, where a trailing `*` is a prefix match) are
            persisted; all other keys are
            silently dropped. Surviving keys are canonicalized to
            lower_snake_case (`X-AI-Agent` → `x_ai_agent`) so joins are
            deterministic regardless of header casing. Non-string values are
            dropped; values are capped at 256 bytes and the map at 10 keys
            (surplus dropped, flagged `context_truncated`). The persisted map
            is returned (full) by `GET /api/v1/decisions/{id}/explain` and
            (truncated to 5 keys) by `GET /api/v1/decisions`.
    DecisionCallerIdentity:
      type: object
      description: |
        Gateway-asserted caller identity. `org_id` and `tenant_id` are
        OPTIONAL in the body -- the auth-derived identity from
        `apiAuthMiddleware` is authoritative. In non-community mode,
        body-supplied values MUST match the authenticated identity or
        the request is rejected with HTTP 403.
      properties:
        gateway_id:
          type: string
          description: Identifier of the calling gateway (PEP), for audit trail.
        org_id:
          type: string
          description: |
            Org scope for the decision. In non-community mode, must match
            the authenticated identity if supplied.
        tenant_id:
          type: string
          description: |
            Tenant scope for the decision. In non-community mode, must
            match the authenticated identity if supplied.
    DecisionTarget:
      type: object
      description: What the gateway is about to call.
      properties:
        type:
          type: string
          description: 'Target type: llm, tool, or agent.'
        model:
          type: string
          description: Model identifier when type is llm.
          example: gpt-4o
        provider:
          type: string
          description: Provider identifier when type is llm.
          example: openai
        server:
          type: string
          description: Server/connector identifier when type is tool (#2904).
          example: postgres
        tool:
          type: string
          description: Tool identifier when type is tool.
          example: query
    DecideResponse:
      type: object
      required:
        - verdict
        - decision_id
        - trace_id
        - obligations
        - evaluated_policies
        - expires_at
      properties:
        verdict:
          type: string
          enum:
            - allow
            - deny
            - needs_approval
          description: |
            The PEP MUST enforce this verdict. `allow` = forward;
            `deny` = block; `needs_approval` = hold for human review
            (enterprise-only -- community mode auto-allows on the
            require_approval action).
        decision_id:
          type: string
          format: uuid
          description: |
            Fresh UUID per decision. Stable handle for audit-log
            correlation, follow-up explain calls, and PEP-side logging.
        trace_id:
          type: string
          minLength: 32
          maxLength: 32
          pattern: ^[0-9a-f]{32}$
          description: |
            W3C trace-context trace-id (32 lowercase hex). When the
            request carried a `traceparent` header, the trace-id is
            reused so multi-gateway-layer decisions stitch into one
            end-to-end trace. Otherwise a fresh trace-id is minted.
        stage:
          type: string
          enum:
            - llm
            - tool
            - agent
          description: Echo of the request stage, for audit dashboards.
        reasons:
          type: array
          items:
            type: string
          description: |
            Human-readable reason strings backing the verdict. Empty on
            verdict=allow with no obligations.
        obligations:
          type: array
          items:
            $ref: '#/components/schemas/DecisionObligation'
          description: |
            PEP-side requirements that accompany an `allow` verdict
            (e.g. redact PII before forwarding). Always a non-nil array
            so PEP code can iterate without a nil-check.
        evaluated_policies:
          type: array
          items:
            type: string
          description: |
            Policy IDs that MATCHED during evaluation (not the total
            number of policies considered). Empty when no policy matched.
            On `deny`, the first entry is the blocking policy; the rest
            (if any) are non-blocking matches recorded for audit.
            On `allow` with obligations, the entries are the policies
            that produced the obligation. The full evaluation count
            will be surfaced separately when the explain endpoint
            (`/api/v1/decisions/{id}/explain`) lands.
        expires_at:
          type: string
          format: date-time
          description: |
            When the decision expires. PEPs that cache decisions MUST
            re-call by this timestamp.
    DecisionObligation:
      type: object
      required:
        - type
      description: |
        A PEP-side requirement attached to an `allow` verdict. Obligations are
        SELF-DESCRIBING and ENGINE-FULFILLABLE (ADR-056 / ADR-057, #2563):
        `/decide` is a pure PDP and never mutates content, so a `redact_pii`
        obligation is not "redact this yourself with your own patterns" — it is
        "call the AxonFlow engine endpoint named in `fulfillment` to obtain
        engine-redacted content." Client-side redaction is forbidden; the
        blessed client path is `platform/shared/pep`.
      properties:
        type:
          type: string
          description: |
            Obligation kind. Currently emitted: `redact_pii`. Future
            obligations will be added here as needed by PEP adapters.
          example: redact_pii
        detail:
          type: string
          description: Human-readable detail for audit logs.
        fulfillment:
          $ref: '#/components/schemas/ObligationFulfillment'
    ObligationFulfillment:
      type: object
      description: |
        Names the engine call a PEP makes to discharge an obligation (since
        8.6.0). Fulfillment is a property of the contract, not of PEP-author
        discipline: a conforming PEP POSTs the obligation's source content to
        `endpoint` and forwards the engine-redacted content the endpoint
        returns. There is no other blessed way to satisfy a `redact_pii`
        obligation. A PEP holding content of a type NOT in `content_types`
        (e.g. an image awaiting OCR-PII redaction) MUST fail closed rather than
        forward it unredacted.
      required:
        - endpoint
        - method
        - phase
      properties:
        endpoint:
          type: string
          description: |
            Engine path the PEP POSTs to in order to discharge the obligation.
            For a request-phase `redact_pii` obligation this is
            `/api/v1/mcp/check-input`; the response-phase counterpart is
            `/api/v1/mcp/check-output`. `/decide` runs pre-call, so it only
            ever emits request-phase obligations.
          example: /api/v1/mcp/check-input
        method:
          type: string
          description: HTTP method to use against `endpoint`.
          example: POST
        phase:
          type: string
          enum:
            - request
            - response
          description: |
            Which content the PEP submits. `request` = the PEP redacts the
            request it is about to forward (the `query` it asked `/decide`
            about); `response` = the PEP redacts a backend response before
            returning it. `/decide` emits only `request` obligations; the
            `response` value is part of the contract for PEP helpers that fan
            out to both phases.
        content_types:
          type: array
          items:
            type: string
          description: |
            The mime-types `endpoint`'s redaction detectors can handle today
            (e.g. `text/plain`). Deliberately content-type-agnostic: adding a
            modality is a server-side detector registration plus a new entry
            here, not a redesign of this shape.
          example:
            - text/plain
    DecideErrorResponse:
      type: object
      required:
        - error
        - verdict
      properties:
        error:
          type: string
          description: Human-readable error message.
        verdict:
          type: string
          enum:
            - deny
          description: |
            Always `deny` on error responses. PEP code can treat the
            envelope as a deny verdict for fail-closed enforcement, or
            inspect the HTTP status code to apply a different posture
            (e.g. fail-open on 503).
        decision_id:
          type: string
          format: uuid
          description: |
            Decision ID for the request. Since #2643 the agent mints
            decision_id and trace_id BEFORE decoding the body, so in
            practice both fields are present on every error the decide
            handler writes, including 400s. The fields remain optional
            in the schema (omitted-when-empty on the wire).
        trace_id:
          type: string
          minLength: 32
          maxLength: 32
          pattern: ^[0-9a-f]{32}$
          description: |
            W3C trace-id for the request. Minted before body decode
            (see decision_id note) — present on every handler-written
            error in practice; optional in the schema.
    ChatCompletionRequest:
      type: object
      required:
        - model
        - messages
      properties:
        model:
          type: string
          description: ID of the model to use (e.g. gpt-4o, gpt-4o-mini).
          example: gpt-4o
        messages:
          type: array
          items:
            type: object
            required:
              - role
              - content
            properties:
              role:
                type: string
                enum:
                  - system
                  - user
                  - assistant
                  - tool
              content:
                description: Message content (string or array for multimodal).
              name:
                type: string
              tool_calls:
                type: array
                items:
                  type: object
              tool_call_id:
                type: string
          minItems: 1
        temperature:
          type: number
          minimum: 0
          maximum: 2
        top_p:
          type: number
        max_tokens:
          type: integer
        max_completion_tokens:
          type: integer
        stream:
          type: boolean
          description: |
            Must be false or omitted. Streaming is not supported in this
            release; setting stream=true returns HTTP 400.
        stop:
          description: Up to 4 stop sequences.
        presence_penalty:
          type: number
        frequency_penalty:
          type: number
        user:
          type: string
        response_format:
          type: object
        seed:
          type: integer
        tools:
          type: array
          items:
            type: object
        tool_choice:
          description: Tool choice configuration.
    ChatCompletionResponse:
      type: object
      required:
        - id
        - object
        - created
        - model
        - choices
      properties:
        id:
          type: string
          description: Unique identifier for the completion.
          example: chatcmpl-abc123
        object:
          type: string
          enum:
            - chat.completion
        created:
          type: integer
          description: Unix timestamp of creation.
        model:
          type: string
          description: Model used for the completion.
        choices:
          type: array
          items:
            type: object
            properties:
              index:
                type: integer
              message:
                type: object
                properties:
                  role:
                    type: string
                  content:
                    type: string
                    nullable: true
                  tool_calls:
                    type: array
                    items:
                      type: object
              finish_reason:
                type: string
                nullable: true
                enum:
                  - stop
                  - length
                  - tool_calls
                  - content_filter
                  - null
        usage:
          type: object
          properties:
            prompt_tokens:
              type: integer
            completion_tokens:
              type: integer
            total_tokens:
              type: integer
        system_fingerprint:
          type: string
    OpenAIErrorResponse:
      type: object
      required:
        - error
      properties:
        error:
          type: object
          required:
            - message
            - type
          properties:
            message:
              type: string
              description: Human-readable error message.
            type:
              type: string
              description: Error type (policy_violation, invalid_request_error, etc.).
              enum:
                - policy_violation
                - invalid_request_error
                - authentication_error
                - server_error
            param:
              type: string
              nullable: true
            code:
              type: string
              description: Machine-readable error code.
              example: policy_denied
    AuditLLMCallRequest:
      type: object
      required:
        - context_id
        - client_id
        - provider
        - model
        - token_usage
      properties:
        context_id:
          type: string
          description: Context ID from pre-check
        client_id:
          type: string
          description: Client application ID
        response_summary:
          type: string
          description: Brief summary of LLM response (for audit)
          maxLength: 500
        provider:
          type: string
          description: LLM provider name
          enum:
            - openai
            - azure-openai
            - anthropic
            - bedrock
            - ollama
            - gemini
        model:
          type: string
          description: Model identifier
          example: gpt-4
        token_usage:
          $ref: '#/components/schemas/TokenUsage'
        latency_ms:
          type: integer
          description: LLM call latency in milliseconds
        metadata:
          type: object
          additionalProperties: true
          description: Additional metadata for audit
    TokenUsage:
      type: object
      properties:
        prompt_tokens:
          type: integer
          description: Tokens in the prompt
        completion_tokens:
          type: integer
          description: Tokens in the completion
        total_tokens:
          type: integer
          description: Total tokens used
    AuditLLMCallResponse:
      type: object
      properties:
        success:
          type: boolean
        audit_id:
          type: string
          description: Unique audit record ID
    Client:
      type: object
      properties:
        id:
          type: string
          description: Unique client identifier
        name:
          type: string
          description: Client application name
        org_id:
          type: string
          description: Organization ID for usage tracking
        tenant_id:
          type: string
          description: Tenant ID for multi-tenancy
        permissions:
          type: array
          items:
            type: string
          description: Granted permissions
        rate_limit:
          type: integer
          description: Requests per minute limit
        enabled:
          type: boolean
          description: Whether client is active
        license_tier:
          type: string
          enum:
            - Community
            - starter
            - professional
            - enterprise
          description: License tier
        license_expiry:
          type: string
          format: date-time
          description: When license expires
    MCPQueryRequest:
      type: object
      required:
        - client_id
        - connector
      properties:
        client_id:
          type: string
        license_key:
          type: string
          description: Can also be provided in X-License-Key header
        user_token:
          type: string
        connector:
          type: string
          description: Connector name
        operation:
          type: string
          description: Operation name (for API connectors like Amadeus)
        statement:
          type: string
          description: SQL/CQL statement (for database connectors)
        parameters:
          type: object
          additionalProperties: true
          description: Query parameters
        limit:
          type: integer
          description: Maximum rows to return
        timeout:
          type: string
          description: Timeout duration (e.g., "10s")
    MCPQueryResponse:
      type: object
      properties:
        success:
          type: boolean
        connector:
          type: string
        data:
          type: array
          items:
            type: object
          description: Query results
        row_count:
          type: integer
        duration_ms:
          type: integer
        redacted:
          type: boolean
          description: >-
            Whether any fields in the response were redacted by policy
            enforcement
        redacted_fields:
          type: array
          items:
            type: string
          description: JSON paths of fields that were redacted (e.g., "data.rows[0].ssn")
        policy_info:
          $ref: '#/components/schemas/PolicyInfo'
    PolicyInfo:
      type: object
      description: Policy evaluation information included in MCP responses
      properties:
        policies_evaluated:
          type: integer
          description: Number of policies evaluated during request/response processing
        blocked:
          type: boolean
          description: Whether the request was blocked by policy
        block_reason:
          type: string
          description: Reason if the request was blocked
        redactions_applied:
          type: integer
          description: Number of field redactions applied to the response
        processing_time_ms:
          type: integer
          description: Time spent on policy evaluation in milliseconds
        matched_policies:
          type: array
          items:
            $ref: '#/components/schemas/PolicyMatchInfo'
          description: Policies that matched during evaluation
        exfiltration_check:
          $ref: '#/components/schemas/ExfiltrationCheckInfo'
        dynamic_policy_info:
          $ref: '#/components/schemas/DynamicPolicyInfo'
    ExplainPolicy:
      type: object
      description: |
        Per-policy explainability record (ADR-043). Surfaced in
        DecisionExplanation.policy_matches (the explain_decision MCP tool).
        Note: the standalone `/api/v1/mcp/check-output` response does NOT
        carry policy_matches (#2870); check-input's policy_matches uses the
        leaner RicherPolicyMatch shape.
      properties:
        policy_id:
          type: string
          description: Unique policy identifier.
        policy_name:
          type: string
          description: Human-readable policy name.
        action:
          type: string
          description: Action taken for this policy match (e.g. "block", "redact", "warn").
        risk_level:
          type: string
          enum:
            - low
            - medium
            - high
            - critical
          description: Risk level configured on this policy.
        allow_override:
          type: boolean
          description: Whether this policy permits a session override.
        policy_description:
          type: string
          description: Optional description text shown to operators.
    RicherPolicyMatch:
      type: object
      description: |
        Per-policy match record on MCP check-input responses
        (`platform/agent/mcp_handler.go`).
      properties:
        policy_id:
          type: string
          description: Unique policy identifier.
        policy_name:
          type: string
          description: Human-readable policy name. Omitted when unknown.
        risk_level:
          type: string
          enum:
            - low
            - medium
            - high
            - critical
          description: Risk level configured on this policy. Omitted when unset.
        allow_override:
          type: boolean
          description: Whether this policy permits a session override.
        policy_version:
          type: integer
          description: Policy version that matched. Omitted when zero.
    ExplainRule:
      type: object
      description: |
        Per-rule explainability record (ADR-043). Surfaced in
        DecisionExplanation.matched_rules to attribute a decision to the
        specific rule that fired.
      properties:
        policy_id:
          type: string
          description: Parent policy identifier.
        rule_id:
          type: string
          description: Unique rule identifier within the policy.
        rule_text:
          type: string
          description: The rule text or pattern that matched.
        matched_on:
          type: string
          description: The portion of the input/output that matched the rule.
    DecisionExplanation:
      type: object
      description: |
        Full explainability payload (ADR-043). Returned by the
        explain_decision MCP tool given a decision_id.
      properties:
        decision_id:
          type: string
          description: Unique audit correlator (matches MCPCheck*Response.decision_id).
        timestamp:
          type: string
          format: date-time
          description: When the decision was made.
        policy_matches:
          type: array
          items:
            $ref: '#/components/schemas/ExplainPolicy'
          description: All policies that matched in this decision.
        matched_rules:
          type: array
          items:
            $ref: '#/components/schemas/ExplainRule'
          description: All specific rules that fired (sub-policy level).
        decision:
          type: string
          enum:
            - allow
            - block
            - redact
            - warn
          description: Final decision applied to the request.
        reason:
          type: string
          description: Human-readable explanation of the decision.
        risk_level:
          type: string
          enum:
            - low
            - medium
            - high
            - critical
          description: Highest risk level across all matches.
        override_available:
          type: boolean
          description: Whether a session override is available for this decision.
        override_existing_id:
          type: string
          description: ID of an active override consumed by this decision (if any).
        historical_hit_count_session:
          type: integer
          description: |
            Number of times this decision-class has fired in the current
            session — useful for surfacing repeat-offender context.
        policy_source_link:
          type: string
          description: Optional URL pointing to the policy definition source.
        tool_signature:
          type: string
          description: Optional signature identifying the calling tool/connector.
    ExfiltrationCheckInfo:
      type: object
      description: Information about exfiltration limit checks (v3.2.0+)
      properties:
        exceeded:
          type: boolean
          description: Whether any exfiltration limit was exceeded
        limit_type:
          type: string
          enum:
            - rows
            - bytes
            - none
          description: Type of limit that was exceeded (if any)
        rows_returned:
          type: integer
          description: Number of rows in the response
        row_limit:
          type: integer
          description: Configured row limit (MCP_MAX_ROWS_PER_QUERY)
        bytes_returned:
          type: integer
          description: Response size in bytes
        byte_limit:
          type: integer
          description: Configured byte limit (MCP_MAX_BYTES_PER_QUERY)
    DynamicPolicyInfo:
      type: object
      description: >
        Information about dynamic policy evaluation via Orchestrator (v3.2.0+).

        Source of truth: `platform/shared/policy/types.go`
        (`DynamicPolicyInfo`).
      properties:
        policies_evaluated:
          type: integer
          description: Number of dynamic policies evaluated
        matched_policies:
          type: array
          items:
            $ref: '#/components/schemas/DynamicPolicyMatch'
          description: Details of matched dynamic policies (omitted when empty)
        orchestrator_reachable:
          type: boolean
          description: Whether the Orchestrator was reachable for dynamic policy evaluation
        processing_time_ms:
          type: integer
          format: int64
          description: Time spent evaluating dynamic policies in milliseconds
    DynamicPolicyMatch:
      type: object
      description: Information about a matched dynamic policy
      properties:
        policy_id:
          type: string
          description: Unique identifier of the matched policy
        policy_name:
          type: string
          description: Human-readable policy name
        policy_type:
          type: string
          description: Type of policy (e.g., rate-limit, budget, time-based, role-based)
        action:
          type: string
          enum:
            - allow
            - deny
            - warn
          description: Action taken by the policy
        message:
          type: string
          description: Optional message from the policy evaluation
    PolicyMatchInfo:
      type: object
      description: Information about a policy match during evaluation
      properties:
        policy_id:
          type: string
          description: Unique policy identifier
        policy_name:
          type: string
          description: Human-readable policy name
        category:
          type: string
          description: Policy category (e.g., "pii-us", "security-sqli")
        severity:
          type: string
          description: Match severity (low, medium, high, critical)
        action:
          type: string
          description: Action taken (block, redact, warn, log)
    MCPExecuteRequest:
      type: object
      required:
        - client_id
        - connector
        - action
      properties:
        client_id:
          type: string
        license_key:
          type: string
        user_token:
          type: string
        connector:
          type: string
        operation:
          type: string
        action:
          type: string
          enum:
            - INSERT
            - UPDATE
            - DELETE
        statement:
          type: string
        parameters:
          type: object
          additionalProperties: true
        timeout:
          type: string
    MCPExecuteResponse:
      type: object
      properties:
        success:
          type: boolean
        connector:
          type: string
        rows_affected:
          type: integer
        duration_ms:
          type: integer
        message:
          type: string
        redacted:
          type: boolean
          description: Whether any fields in the response were redacted by policy
        redacted_fields:
          type: array
          items:
            type: string
          description: JSON paths of fields that were redacted
        policy_info:
          $ref: '#/components/schemas/PolicyInfo'
    MCPCheckInputRequest:
      type: object
      required:
        - connector_type
        - statement
      properties:
        client_id:
          type: string
          description: Client identifier (required in Enterprise mode)
        user_token:
          type: string
          description: JWT user token (required in Enterprise mode)
        tenant_id:
          type: string
          description: >-
            Tenant identifier (required in Enterprise mode, defaults to
            "default" in Community)
        user_id:
          type: string
          description: Optional user identifier for dynamic policy evaluation
        user_role:
          type: string
          description: >-
            Optional user role for role-based access policies (e.g., "admin",
            "analyst")
        connector_type:
          type: string
          description: >-
            MCP connector/server type (e.g., "postgres", "snowflake",
            "salesforce")
        tool:
          type: string
          description: >-
            Optional tool identifier being invoked, distinct from
            connector_type/server (#2904). Feeds capability-scoped policy
            evaluation when set.
        statement:
          type: string
          description: The SQL query or command to validate against policies
        parameters:
          type: object
          additionalProperties: true
          description: >
            Optional query parameters. Values are individually scanned for SQLi,

            PII, and compliance violations by the static policy engine. String

            values are scanned directly; nested objects/arrays are
            JSON-serialized

            before scanning; numeric values are converted to strings for

            PII/compliance detection. Boolean values are skipped.
        operation:
          type: string
          enum:
            - query
            - execute
          default: execute
          description: >-
            Operation type — affects dynamic policy evaluation (rate limits may
            differ)
        content_type:
          type: string
          description: |
            Declared content type of `statement` (ADR-056). Defaults to
            `text/plain` when omitted. When set to a value no registered
            detector handles, the request is rejected with **415** and a
            canonical blocked audit row tagged `content_type_unsupported`
            is written (fail-closed; see the 415 response). Source of
            truth: `platform/agent/mcp_handler.go` (MCPCheckInputRequest).
    MCPCheckInputResponse:
      type: object
      properties:
        allowed:
          type: boolean
          description: Whether the input passed all policy checks
        block_reason:
          type: string
          description: Human-readable reason if blocked (omitted when allowed)
        policies_evaluated:
          type: integer
          description: Total number of policies evaluated
        policy_info:
          $ref: '#/components/schemas/PolicyInfo'
        decision_id:
          type: string
          description: |
            Unique audit correlator for this policy decision. Links the gate
            response to its row in the audit log; surfaceable to end users
            for explainability ("decision: dec_abc123").
        risk_level:
          type: string
          enum:
            - low
            - medium
            - high
            - critical
          description: |
            Highest risk level across all matched policies. Plugins use this
            to map the block reason to severity (warning vs hard error).
        policy_matches:
          type: array
          items:
            $ref: '#/components/schemas/RicherPolicyMatch'
          description: |
            Per-policy explainability records (ADR-043). Surfaced when
            dynamic policy evaluation produces non-empty matches. Source
            of truth: `platform/agent/mcp_handler.go` (RicherPolicyMatch).
        override_available:
          type: boolean
          description: |
            Whether at least one matched policy permits a session override.
            Used by plugins to render an "override via explain_decision"
            hint when the block is overrideable.
        override_existing_id:
          type: string
          description: |
            ID of an active override consumed by this decision (if any).
            Distinguishes "you already have an override" from "you can
            request one".
        redacted:
          type: boolean
          description: |
            Whether the engine masked any PII in the request statement.
            Omitted (false) when nothing was redacted.
        redacted_statement:
          type: string
          description: |
            The request statement with PII fields masked. A PEP fulfilling a
            Decision Mode redact_pii obligation forwards THIS value instead of
            the original. Omitted when nothing was redacted. Source of truth:
            `platform/agent/mcp_handler.go` (MCPCheckInputResponse).
        redaction_evaluated:
          type: boolean
          description: |
            Whether the redaction detector actually ran (regardless of whether
            it masked anything). A PEP fulfilling a redact_pii obligation MUST
            fail closed when this is false — it means no detection config was
            enabled, so `redacted: false` is indistinguishable from "looked,
            found nothing" and the request must not be forwarded as clean.
    MCPCheckOutputRequest:
      type: object
      required:
        - connector_type
      anyOf:
        - required:
            - response_data
        - required:
            - message
      properties:
        client_id:
          type: string
          description: Client identifier (required in Enterprise mode)
        user_token:
          type: string
          description: JWT user token (required in Enterprise mode)
        tenant_id:
          type: string
          description: >-
            Tenant identifier (required in Enterprise mode, defaults to
            "default" in Community)
        user_id:
          type: string
          description: Optional user identifier
        connector_type:
          type: string
          description: >-
            MCP connector/server type (e.g., "postgres", "snowflake",
            "salesforce")
        tool:
          type: string
          description: >-
            Optional tool identifier whose output is being validated, distinct
            from connector_type/server (#2904/#2955). Feeds capability-scoped
            response evaluation when set (a text-document tool's output skips
            execution-class detectors); omitted → full (fail-closed) evaluation,
            no fallback from connector_type.
        response_data:
          type: array
          items:
            type: object
            additionalProperties: true
          description: Query-style response rows to validate
        message:
          type: string
          description: Execute-style response message (e.g., "5 rows affected")
        metadata:
          type: object
          additionalProperties: true
          description: >-
            Connector metadata for SQLi response scanning (e.g., query echo,
            database name)
        row_count:
          type: integer
          description: Total number of rows returned (used for exfiltration limit checks)
    MCPCheckOutputResponse:
      type: object
      properties:
        allowed:
          type: boolean
          description: Whether the output passed all policy checks
        block_reason:
          type: string
          description: Human-readable reason if blocked (omitted when allowed)
        redacted_data:
          description: |
            Response data with PII fields masked. For query-style checks
            (tabular `response_data`) this carries the masked rows; for
            execute-style checks (`message`) it carries the masked message
            string. Omitted if no redaction was needed.

            ⚠️ **Contract pending (#2870):** unlike the `/api/v1/mcp-server`
            JSON-RPC `check_output` tool (which returns a separate
            `redacted_message` field), this standalone REST endpoint returns
            only `redacted_data` — it never emits `redacted_message`, even
            though several SDK response types model that field. Do not rely
            on `redacted_message` here until #2870 lands. Source of truth:
            `platform/agent/mcp_handler.go` (MCPCheckOutputResponse).
        policies_evaluated:
          type: integer
          description: Total number of policies evaluated
        exfiltration_info:
          $ref: '#/components/schemas/ExfiltrationCheckInfo'
        policy_info:
          $ref: '#/components/schemas/PolicyInfo'
        decision_id:
          type: string
          description: Unique audit correlator for this policy decision.
        redaction_evaluated:
          type: boolean
          description: |
            Whether the response-phase redaction pipeline actually ran
            (#2865). Mirrors MCPCheckInputResponse.redaction_evaluated for the
            response leg. A PEP fulfilling a response-phase redact_pii
            obligation MUST fail closed when this is false/absent — the
            redactor did not run (detection disabled for the connector, or no
            policy engine), so absence of `redacted_data` cannot be trusted as
            "nothing to mask." Omitted (false) preserves the pre-#2865 shape.
    ConnectorRefreshResponse:
      type: object
      description: |
        Response from connector cache refresh operations. Source of truth:
        `platform/agent/connector_refresh_api.go` (`ConnectorRefreshResponse`,
        lines 61-69).
      properties:
        success:
          type: boolean
          description: Whether the refresh operation succeeded
        message:
          type: string
          description: |
            Human-readable status message. Also the signal for which scope
            the credential bought: `Tenant connector caches refreshed` vs
            `All connector caches refreshed`.
        scope:
          type: string
          enum:
            - all
            - tenant
            - connector
          description: Which route produced this response
        tenant_id:
          type: string
          description: |
            The authenticated tenant. Omitted on a deployment-wide refresh
            (internal-service credential on `POST /api/v1/connectors/refresh`),
            because no single tenancy describes the work done.
        connector:
          type: string
          description: Connector name (when scope is connector)
        duration:
          type: string
          description: Duration of the refresh operation
          example: 12.345ms
        stats:
          $ref: '#/components/schemas/ConnectorRefreshStats'
      required:
        - success
        - message
        - scope
        - duration
    ConnectorRefreshStats:
      type: object
      description: |
        Cache statistics after refresh. Source of truth:
        `platform/agent/connector_refresh_api.go` (`RefreshStatsInfo`, lines
        83-88).

        This object carries `cached_connectors` **and nothing else**. It used
        to also declare `hits`, `misses`, `evictions` and `hit_rate_percent`;
        no producer ever set them, so they always serialized as misleading
        hard zeros, and they are deployment-wide figures that disclose other
        tenants' cache activity. They were removed from the struct in #3067 —
        read them from the `deployment` block of
        `GET /api/v1/connectors/cache/stats` (internal-service credential) or
        from the un-scoped `/prometheus` gauges.
      properties:
        cached_connectors:
          type: integer
          format: int64
          description: |
            Cached-connector count *after* the eviction, scoped to the work
            just done: the caller's own count on a tenant refresh
            (`tenantRefreshStats`, lines 404-408), the deployment-wide count
            on an internal-service deployment-wide refresh
            (`deploymentRefreshStats`, lines 415-419).

            Structurally `0` on `POST /api/v1/connectors/refresh` and
            `POST /api/v1/connectors/refresh/{tenant_id}` — those routes evict
            the whole scope immediately before counting it. Only informative
            on `.../{tenant_id}/{connector_name}`.
    ConnectorCacheStats:
      type: object
      description: |
        Connector cache statistics. Source of truth:
        `platform/agent/connector_refresh_api.go`
        (`connectorCacheStatsHandler`, lines 350-369).

        **Tenant-scoped.** A Basic-auth caller receives exactly four
        top-level fields — `cached_connectors`, `registry_enabled`,
        `tenant_id`, `timestamp` — and no cache-health counters at all. The
        counters live in `deployment`, which is present **only** for the
        internal-service credential.

        Before #3067 all of the counters were served flat at the top level to
        anonymous callers; the flat shape no longer exists on any code path.
      properties:
        tenant_id:
          type: string
          description: |
            The authenticated tenant `cached_connectors` is scoped to. For an
            internal-service caller this is whatever `X-Tenant-ID` was sent,
            or the synthetic `orchestrator-internal` client id when it was
            not (`platform/agent/authenticator.go:127-130`).
          example: acme-corp
        cached_connectors:
          type: integer
          description: |
            The number of connectors cached **for `tenant_id`**
            (`registry.CountByTenant`), not for the deployment. It is
            typically `0` for an internal-service caller that sent no
            `X-Tenant-ID` — read `deployment.cached_connectors` instead.
        registry_enabled:
          type: boolean
          description: |
            Always `true` when the endpoint answers 200 — a disabled or
            uninitialized registry returns 503 before this body is built.
        timestamp:
          type: string
          format: date-time
          description: Server time (UTC) at which the body was built
        deployment:
          type: object
          description: |
            Deployment-wide operator telemetry. **Present only for the
            internal-service credential**
            (`platform/agent/connector_refresh_api.go:356`). Absent for every
            tenant caller; its absence must not be read as zero.

            Counter values come from `TenantRegistryStats`
            (`platform/agent/tenant_connector_registry.go:131-141`).
          properties:
            cached_connectors:
              type: integer
              description: Deployment-wide cached-connector count (`registry.Count()`)
            hits:
              type: integer
              format: int64
              description: Total cache hits, deployment-wide
            misses:
              type: integer
              format: int64
              description: Total cache misses, deployment-wide
            evictions:
              type: integer
              format: int64
              description: Total manual evictions, deployment-wide
            factory_creations:
              type: integer
              format: int64
              description: Successful connector factory creations
            factory_failures:
              type: integer
              format: int64
              description: Failed connector factory creations
            connection_errors:
              type: integer
              format: int64
              description: Connector initialization errors
            hit_rate_percent:
              type: number
              format: double
              description: |
                `hits / (hits + misses) * 100`, deployment-wide; `0` when
                neither counter has moved
                (`platform/agent/tenant_connector_registry.go:525-534`).
            last_eviction:
              type: string
              format: date-time
              description: |
                Timestamp of the last eviction. Serializes as the Go zero
                time `0001-01-01T00:00:00Z` when nothing has been evicted yet.
            last_factory_create:
              type: string
              format: date-time
              description: |
                Timestamp of the last factory creation. Serializes as the Go
                zero time `0001-01-01T00:00:00Z` when no connector has been
                created yet.
    ConnectorListResponse:
      type: object
      properties:
        connectors:
          type: array
          items:
            type: object
            properties:
              name:
                type: string
              type:
                type: string
              version:
                type: string
              healthy:
                type: boolean
              latency_ms:
                type: integer
              capabilities:
                type: array
                items:
                  type: string
              error:
                type: string
        count:
          type: integer
    ConnectorHealthResponse:
      type: object
      properties:
        healthy:
          type: boolean
        latency_ms:
          type: integer
        last_check:
          type: string
          format: date-time
        error:
          type: string
    ErrorResponse:
      type: object
      description: |
        Handler-written error envelope. Note the agent has a second error
        envelope for middleware-written errors (see JSONError) — clients
        should tolerate both shapes on 4xx/5xx.
      properties:
        success:
          type: boolean
          example: false
        error:
          type: string
          description: Error message
    JSONError:
      type: object
      description: |
        Middleware-written error envelope (auth middleware 401s, static
        policy API errors). Source of truth:
        `platform/agent/static_policy_api_handlers.go` (writeJSONError).
      properties:
        error:
          type: object
          properties:
            code:
              type: integer
              description: HTTP status code
            message:
              type: string
    RateLimitEnvelope:
      type: object
      description: |
        Shared tier rate-limit envelope written by the Community SaaS
        limiter for daily-quota 429s (the same shape is used with 403 for
        Pro-only feature limits). Per-minute 429s use a plain
        `{"error": "..."}` body with only a Retry-After header — not this
        envelope. Accompanied by the
        `X-Axonflow-Tier-Limit`, `X-Axonflow-Upgrade-URL`, and
        `Retry-After` headers. Source of truth:
        `platform/agent/community_saas_ratelimit_response.go`
        (rateLimitEnvelope).
      properties:
        error:
          type: string
        limit_type:
          type: string
          description: Which limiter fired (e.g. daily_quota)
        tier:
          type: string
        limit:
          type: integer
        remaining:
          type: integer
        window:
          type: string
        resets_at:
          type: string
          format: date-time
        upgrade:
          type: object
          properties:
            tier:
              type: string
            wording:
              type: string
            compare_url:
              type: string
            buy_url:
              type: string
    DecisionEntry:
      type: object
      description: |
        A single decision step in an AI decision chain.
        EU AI Act Article 12 requires automatic recording of AI system events.
      required:
        - chain_id
        - request_id
        - org_id
        - tenant_id
        - decision_type
        - decision_outcome
      properties:
        id:
          type: string
          format: uuid
          description: Unique identifier for this decision entry
        chain_id:
          type: string
          format: uuid
          description: |
            Links related decisions in a multi-step workflow.
            All decisions in the same workflow share this ID.
        request_id:
          type: string
          format: uuid
          description: Unique identifier for the specific request
        parent_request_id:
          type: string
          format: uuid
          description: Previous request in the chain (null for first step)
        step_number:
          type: integer
          description: Order within the chain (starts at 1)
          minimum: 1
        org_id:
          type: string
          format: uuid
          description: Organization identifier
        tenant_id:
          type: string
          description: Tenant identifier
        client_id:
          type: string
          description: Client application identifier
        user_id:
          type: string
          description: User identifier
        decision_type:
          type: string
          enum:
            - policy_enforcement
            - llm_generation
            - data_retrieval
            - human_review
            - system_action
          description: |
            Type of AI decision:
            - policy_enforcement: Static policy evaluation
            - llm_generation: LLM model invocation
            - data_retrieval: Data source query
            - human_review: HITL decision
            - system_action: Automated action
        decision_outcome:
          type: string
          enum:
            - approved
            - blocked
            - modified
            - pending_review
            - error
          description: |
            Result of the decision:
            - approved: Request allowed
            - blocked: Denied by policy
            - modified: Content filtered
            - pending_review: Awaiting human review
            - error: Processing error
        system_id:
          type: string
          description: AI system identifier (e.g., "axonflow-agent/1.0.0")
        model_provider:
          type: string
          description: LLM provider (if applicable)
        model_id:
          type: string
          description: Specific model identifier
        policies_evaluated:
          type: array
          items:
            type: string
          description: List of policy IDs that were evaluated
        policy_triggered:
          type: string
          description: Policy that caused block/modify (if any)
        risk_level:
          type: string
          enum:
            - minimal
            - limited
            - high
            - unacceptable
          description: |
            EU AI Act risk classification:
            - minimal: No significant risk
            - limited: Transparency obligations
            - high: Conformity assessment required
            - unacceptable: Prohibited AI use
        requires_human_review:
          type: boolean
          description: Whether human review is required
        processing_time_ms:
          type: integer
          description: Processing time in milliseconds
        data_sources:
          type: array
          items:
            type: string
          description: Data sources queried
        audit_hash:
          type: string
          description: SHA-256 hash for tamper detection
        created_at:
          type: string
          format: date-time
          description: When the decision was recorded
    ChainSummary:
      type: object
      description: Aggregate statistics for a decision chain
      properties:
        chain_id:
          type: string
          format: uuid
        total_steps:
          type: integer
          description: Number of decisions in the chain
        total_processing_time_ms:
          type: integer
          description: Sum of all processing times
        has_blocked:
          type: boolean
          description: Whether any decision was blocked
        requires_review:
          type: boolean
          description: Whether human review is required
        highest_risk_level:
          type: string
          enum:
            - minimal
            - limited
            - high
            - unacceptable
          description: Highest risk level in the chain
        first_decision_at:
          type: string
          format: date-time
        last_decision_at:
          type: string
          format: date-time
        decision_types:
          type: array
          items:
            type: string
          description: Unique decision types in the chain
        total_policies_applied:
          type: integer
          description: Count of unique policies evaluated
    HITLCreateRequest:
      type: object
      description: |
        Body for POST /api/v1/hitl/queue. Org/tenant identity comes from the
        `X-Org-ID` / `X-Tenant-ID` headers, not the body. Source of truth:
        `platform/agent/hitl/handler.go` (CreateRequestInput).
      required:
        - client_id
        - original_query
        - request_type
        - triggered_policy_id
        - triggered_policy_name
        - trigger_reason
      properties:
        client_id:
          type: string
        user_id:
          type: string
        original_query:
          type: string
          description: The query/action awaiting human review
        request_type:
          type: string
          description: Request classification (e.g. `mcp_query`, `llm_chat`)
        request_context:
          type: object
          additionalProperties: true
        triggered_policy_id:
          type: string
        triggered_policy_name:
          type: string
        trigger_reason:
          type: string
        severity:
          type: string
          enum:
            - low
            - medium
            - high
            - critical
        eu_ai_act_article:
          type: string
        compliance_framework:
          type: string
        risk_classification:
          type: string
        expires_in_seconds:
          type: integer
          description: TTL before the request auto-expires
        notify_url:
          type: string
          format: uri
          description: |
            Optional outbound webhook URL fired asynchronously after the request
            transitions to a terminal state (approved / rejected / overridden /
            expired). Must use `https://` or `http://`. The platform signs the
            envelope with HMAC-SHA256 over the body keyed by the deployment's
            `AXONFLOW_HITL_WEBHOOK_SIGNING_KEY`, sent as
            `X-AxonFlow-Signature: sha256=<hex>`. See the HITLWebhookEnvelope
            schema and `docs.getaxonflow.com/docs/governance/hitl` for the full
            shape + verification recipe.
    HITLWebhookEnvelope:
      type: object
      description: |
        JSON body POSTed to a HITL request's `notify_url` after a terminal-state
        transition. Verify the `X-AxonFlow-Signature` header (HMAC-SHA256, hex,
        constant-time compare) against the raw body before trusting fields.
      required:
        - approval_id
        - status
        - decided_at
        - original_query
        - request_type
        - severity
      properties:
        approval_id:
          type: string
          format: uuid
        status:
          type: string
          enum:
            - approved
            - rejected
            - overridden
            - expired
        decided_by:
          type: string
          description: >-
            Reviewer email when known; reviewer ID otherwise. Empty for
            `expired`.
        decided_at:
          type: string
          format: date-time
        original_query:
          type: string
        request_type:
          type: string
        severity:
          type: string
          enum:
            - low
            - medium
            - high
            - critical
        decision_envelope:
          type: object
          additionalProperties: true
          description: >-
            Decision-context bag (org_id, tenant_id, client_id,
            triggered_policy_id, comment, justification).
    HITLReviewInput:
      type: object
      description: |
        Body for approve/reject. Source of truth:
        `platform/agent/hitl/handler.go` (ReviewInput).
      properties:
        reviewer_id:
          type: string
        reviewer_email:
          type: string
        reviewer_role:
          type: string
        comment:
          type: string
    HITLApprovalRequest:
      type: object
      description: |
        An approval-queue row. Source of truth:
        `platform/agent/hitl/repository.go` (ApprovalRequest).
      properties:
        id:
          type: integer
          format: int64
        request_id:
          type: string
          format: uuid
        org_id:
          type: string
        tenant_id:
          type: string
        client_id:
          type: string
        user_id:
          type: string
        original_query:
          type: string
        request_type:
          type: string
        request_context:
          type: object
          additionalProperties: true
        triggered_policy_id:
          type: string
        triggered_policy_name:
          type: string
        trigger_reason:
          type: string
        severity:
          type: string
        eu_ai_act_article:
          type: string
        compliance_framework:
          type: string
        risk_classification:
          type: string
        status:
          type: string
          enum:
            - pending
            - approved
            - rejected
            - overridden
            - expired
        reviewer_id:
          type: string
        reviewer_email:
          type: string
        reviewer_role:
          type: string
        review_comment:
          type: string
        reviewed_at:
          type: string
          format: date-time
        override_justification:
          type: string
        override_authorized_by:
          type: string
        notify_url:
          type: string
        expires_at:
          type: string
          format: date-time
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    HITLHistoryEntry:
      type: object
      description: |
        Immutable audit-trail entry for a request. Source of truth:
        `platform/agent/hitl/repository.go` (ApprovalHistory).
      properties:
        id:
          type: integer
          format: int64
        request_id:
          type: string
          format: uuid
        org_id:
          type: string
        tenant_id:
          type: string
        action:
          type: string
        actor_id:
          type: string
        actor_email:
          type: string
        actor_role:
          type: string
        actor_ip:
          type: string
        comment:
          type: string
        justification:
          type: string
        previous_status:
          type: string
        new_status:
          type: string
        created_at:
          type: string
          format: date-time
    CircuitBreakerTripRequest:
      type: object
      description: |
        Request body for trip / emergency-stop. Org and user identity come
        from the `X-Org-ID` (or `X-Tenant-ID`) and `X-User-ID` headers, not
        the body. Source of truth: `platform/agent/circuitbreaker/handler.go`
        (TripRequest).
      properties:
        scope:
          type: string
          enum:
            - global
            - tenant
            - client
            - policy
          default: global
          description: Blast radius of the stop
        scope_id:
          type: string
          description: Required for non-global scopes (tenant/client/policy ID)
        reason:
          type: string
          enum:
            - manual
            - policy_violation
            - risk_level
            - error_rate
          default: manual
        comment:
          type: string
          description: Free-text audit-trail comment
        duration_minutes:
          type: integer
          description: Auto-expire after N minutes; 0 or omitted = indefinite
    CircuitBreakerResetRequest:
      type: object
      description: |
        Request body for reset / emergency-stop release. Org and user
        identity come from headers, not the body. Source of truth:
        `platform/agent/circuitbreaker/handler.go` (ResetRequest).
      properties:
        scope:
          type: string
          enum:
            - global
            - tenant
            - client
            - policy
          default: global
        scope_id:
          type: string
        comment:
          type: string
          description: Free-text audit-trail comment
    CircuitBreaker:
      type: object
      description: |
        A circuit record as returned by the status and history endpoints.
        Source of truth: `platform/agent/circuitbreaker/circuit_breaker.go`
        (Circuit).
      properties:
        id:
          type: string
        scope:
          type: string
          enum:
            - global
            - tenant
            - client
            - policy
        scope_id:
          type: string
        org_id:
          type: string
        state:
          type: string
          enum:
            - closed
            - open
            - half_open
          description: |
            - closed: Normal operation
            - open: circuit tripped — matching requests are blocked
            - half_open: testing recovery — limited requests allowed
              (auto-recovery transition; can appear in status/history)
        trip_reason:
          type: string
          enum:
            - manual
            - policy_violation
            - risk_level
            - error_rate
        tripped_by:
          type: string
        tripped_by_email:
          type: string
        trip_comment:
          type: string
        tripped_at:
          type: string
          format: date-time
        expires_at:
          type: string
          format: date-time
          description: >-
            Auto-expiry time when the trip carried a duration; absent for
            indefinite trips
        reset_by:
          type: string
        reset_at:
          type: string
          format: date-time
        error_count:
          type: integer
        violation_count:
          type: integer
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    CircuitBreakerConfigResponse:
      type: object
      properties:
        source:
          type: string
          enum:
            - global
            - tenant
          description: Whether config is global defaults or tenant-specific
        error_threshold:
          type: integer
          description: Number of errors in window to trigger auto-trip
        violation_threshold:
          type: integer
          description: Number of policy violations in window to trigger auto-trip
        window_seconds:
          type: integer
          description: Sliding window duration for counting events
        default_timeout_seconds:
          type: integer
          description: How long a circuit stays open before auto-recovery
        max_timeout_seconds:
          type: integer
          description: Maximum allowed circuit timeout
        enable_auto_recovery:
          type: boolean
          description: Whether open circuits automatically close after timeout
        tenant_id:
          type: string
          description: Tenant ID (present when source is tenant)
        overrides:
          type: object
          description: Tenant-specific override values (present when source is tenant)
    CircuitBreakerConfigUpdate:
      type: object
      required:
        - tenant_id
      properties:
        tenant_id:
          type: string
        error_threshold:
          type: integer
          nullable: true
        violation_threshold:
          type: integer
          nullable: true
        window_seconds:
          type: integer
          nullable: true
        default_timeout_seconds:
          type: integer
          nullable: true
        max_timeout_seconds:
          type: integer
          nullable: true
        enable_auto_recovery:
          type: boolean
          nullable: true
    CircuitBreakerNotificationConfig:
      type: object
      properties:
        id:
          type: string
        org_id:
          type: string
        tenant_id:
          type: string
        type:
          type: string
          enum:
            - webhook
            - slack
            - pagerduty
        url:
          type: string
        secret:
          type: string
          description: Masked in GET responses
        active:
          type: boolean
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    CircuitBreakerNotificationCreate:
      type: object
      required:
        - type
        - url
      properties:
        type:
          type: string
          enum:
            - webhook
            - slack
            - pagerduty
        url:
          type: string
          description: Webhook URL, Slack incoming webhook URL, or PagerDuty override URL
        secret:
          type: string
          description: HMAC secret for webhooks, or PagerDuty routing key
        tenant_id:
          type: string
          description: Optional tenant filter for notifications
        active:
          type: boolean
          default: true
    CircuitBreakerNotificationUpdate:
      type: object
      properties:
        type:
          type: string
          enum:
            - webhook
            - slack
            - pagerduty
        url:
          type: string
        secret:
          type: string
        tenant_id:
          type: string
        active:
          type: boolean
    ChainVerificationResult:
      type: object
      description: |
        Outcome of verifying an entire decision chain's prev_hash linkage and
        every per-record Ed25519 signature.
      properties:
        chain_id:
          type: string
          format: uuid
        org_id:
          type: string
        total_records:
          type: integer
        valid:
          type: boolean
          description: |
            True when no integrity violation was detected (linkage holds and
            every signature that exists verifies). Can be true for a chain with
            zero signed records. Use `authorship_proven` for the strong claim.
        authorship_proven:
          type: boolean
          description: |
            True only when every record is signed AND all signatures + linkage
            verify. Gate non-repudiation on this flag.
        linkage_valid:
          type: boolean
        signatures_valid:
          type: boolean
        signed_records:
          type: integer
        unsigned_records:
          type: integer
        first_broken_seq:
          type: integer
          format: int64
          description: Sequence of the first detected break (omitted when valid).
        first_broken_record_id:
          type: string
          description: Record id of the first detected break (omitted when valid).
        break_reason:
          type: string
          description: Why the chain broke (omitted when valid).
        signing_key_id:
          type: string
          description: Identifier of the tracker's current signing key.
        public_key:
          type: string
          description: Base64 Ed25519 public key for offline re-verification.
        verified_at:
          type: string
          format: date-time
      required:
        - chain_id
        - org_id
        - total_records
        - valid
        - authorship_proven
        - linkage_valid
        - signatures_valid
        - verified_at
    RecordVerificationResult:
      type: object
      description: >
        Outcome of verifying a SINGLE decision record standalone. Republishes
        the

        recomputed material so the result is independently checkable offline.
      properties:
        record_id:
          type: string
          format: uuid
        chain_id:
          type: string
          format: uuid
        org_id:
          type: string
        chain_seq:
          type: integer
          format: int64
        signed:
          type: boolean
        signature_valid:
          type: boolean
        valid:
          type: boolean
        reason:
          type: string
          description: Explanation when the record does not verify (omitted when valid).
        digest_preimage_b64:
          type: string
          description: >
            Base64 of the exact byte string that SHA-256-hashes to

            `record_digest`. Decode, hash, and confirm it equals
            `record_digest`,

            then rebuild it from the raw record fields to verify independently.
        record_digest:
          type: string
          description: SHA-256 hex of the record pre-image.
        prev_hash:
          type: string
          description: Hash of the previous record in the chain.
        chain_hash:
          type: string
          description: |
            sha256_hex(record_digest + "|" + prev_hash); the message that the
            Ed25519 signature is computed over.
        record_signature:
          type: string
          description: Base64 Ed25519 signature over `chain_hash` (omitted when unsigned).
        signing_key_id:
          type: string
        public_key:
          type: string
          description: Base64 Ed25519 public key for offline re-verification.
        verified_at:
          type: string
          format: date-time
      required:
        - record_id
        - chain_id
        - org_id
        - chain_seq
        - signed
        - signature_valid
        - valid
        - digest_preimage_b64
        - record_digest
        - prev_hash
        - chain_hash
        - verified_at
    AuditSigningKeyResponse:
      type: object
      description: >-
        The current public Ed25519 verification key (or that none is
        configured).
      properties:
        algorithm:
          type: string
          example: ed25519
        signing_key_id:
          type: string
          description: Identifier of the current signing key (empty when none configured).
        public_key:
          type: string
          description: Base64 Ed25519 public key (empty when none configured).
        configured:
          type: boolean
          description: >-
            False when no signing key is configured (records are hash-chained
            but unsigned).
        verification_note:
          type: string
          description: How to verify a record offline with this key.
      required:
        - algorithm
        - configured
    AuditVerifyError:
      type: object
      description: Error envelope returned by the audit-verification endpoints.
      properties:
        error:
          type: string
          description: Human-readable error message.
      required:
        - error
  responses:
    BadRequest:
      description: Invalid request body or parameters
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            success: false
            error: Invalid request body
    Unauthorized:
      description: |
        Missing or invalid authentication. Handler-written 401s use the
        `{success, error}` envelope; 401s written by the auth middleware
        use the `{"error": {"code", "message"}}` envelope (JSONError).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            success: false
            error: >-
              Authentication required: provide Authorization header with Basic
              auth (clientId:clientSecret)
    Forbidden:
      description: Access denied by policy or tenant mismatch
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            success: false
            error: Tenant mismatch
    InternalError:
      description: Internal server error
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            success: false
            error: Internal server error
  headers:
    X-AI-Request-ID:
      description: >
        Unique identifier for this AI interaction (UUID v4).

        Use this to link to the full audit trail for EU AI Act Article 12
        compliance.
      schema:
        type: string
        format: uuid
      example: 550e8400-e29b-41d4-a716-446655440000
    X-AI-Timestamp:
      description: |
        When the AI system processed this request (RFC3339 with nanoseconds).
        Required for EU AI Act Article 12 temporal audit evidence.
      schema:
        type: string
        format: date-time
      example: '2025-01-15T10:30:00.123456789Z'
    X-AI-System-ID:
      description: |
        Identifies the AI system that processed the request.
        Format: "axonflow-agent/{version}" or "axonflow-orchestrator/{version}"
      schema:
        type: string
      example: axonflow-agent/1.0.0
    X-AI-Chain-ID:
      description: >
        Links related AI decisions in a multi-step workflow (UUID v4).

        Same across all related requests for EU AI Act Article 12 decision chain
        traceability.
      schema:
        type: string
        format: uuid
      example: 660e8400-e29b-41d4-a716-446655440001
    X-AI-Processing-Type:
      description: |
        Type of AI processing performed.
        - policy-enforcement: Only policy rules applied
        - llm-generation: LLM model generated content
        - data-retrieval: MCP connector data fetch
        - hybrid: Combination of above
      schema:
        type: string
        enum:
          - policy-enforcement
          - llm-generation
          - data-retrieval
          - hybrid
      example: hybrid
    X-AI-Model-Provider:
      description: >
        LLM provider used (if any). EU AI Act Article 13 transparency
        requirement.
      schema:
        type: string
        enum:
          - openai
          - azure-openai
          - anthropic
          - bedrock
          - ollama
          - gemini
          - none
      example: anthropic
    X-AI-Model-ID:
      description: |
        Specific model version used. EU AI Act Article 13 model traceability.
      schema:
        type: string
      example: claude-opus-4
    X-AI-Policies-Applied:
      description: |
        Comma-separated list of policies evaluated.
        EU AI Act Article 14 human oversight indicator.
      schema:
        type: string
      example: pii-detection,rate-limit,eu-ai-act-article-14
    X-AI-Decision-Blocked:
      description: |
        Whether the request was blocked by policy.
        EU AI Act Article 14 intervention indicator.
      schema:
        type: string
        enum:
          - 'true'
          - 'false'
      example: 'false'
    X-AI-Processing-Time-Ms:
      description: |
        Total AI processing time in milliseconds.
        EU AI Act Article 12 performance monitoring.
      schema:
        type: integer
      example: 150
    X-AI-Risk-Level:
      description: |
        Assessed risk level of the operation.
        EU AI Act Article 6 risk classification transparency.
      schema:
        type: string
        enum:
          - minimal
          - limited
          - high
          - unacceptable
      example: limited
    X-AI-Human-Oversight-Required:
      description: |
        Whether this request requires human review.
        EU AI Act Article 14 human oversight flagging.
      schema:
        type: string
        enum:
          - 'true'
          - 'false'
      example: 'false'
    X-AI-Content-Filtered:
      description: |
        Whether content filtering/redaction was applied.
        EU AI Act Article 13 modification transparency.
      schema:
        type: string
        enum:
          - 'true'
          - 'false'
      example: 'false'
    X-AI-Data-Sources:
      description: |
        Comma-separated list of data sources queried.
        EU AI Act Article 10 data lineage transparency.
      schema:
        type: string
      example: postgres,amadeus
    X-AI-Audit-Hash:
      description: |
        SHA-256 hash of the audit record for tamper-evident verification.
        EU AI Act Article 12 record integrity.
      schema:
        type: string
        pattern: ^[a-f0-9]{64}$
      example: 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
