openapi: 3.0.3
info:
  title: Rasepi Trust API
  version: "1.0"
  description: >
    The machine-facing surface of Rasepi, the trust-control plane for company
    knowledge. External systems (chatbots, RAG pipelines, agents, CI gates) call
    these endpoints before relying on a document. Each source is evaluated against
    freshness, ownership, provenance, policy, sensitivity, and request context, and
    returns a decision: allow, warn, block, or escalate. Every call is recorded in
    the decision ledger and attributed to the calling AI application.
  contact:
    name: Rasepi
    url: https://developers.rasepi.com
servers:
  - url: https://api.rasepi.com
    description: Production
  - url: http://localhost:5000
    description: Local development
security:
  - ApiKeyBearer: []
  - ApiKeyHeader: []
  - OAuth2ClientCredentials: [trust:evaluate, trust:read, trust:usage]
tags:
  - name: Trust
    description: Evaluate and filter knowledge sources before AI uses them.
paths:
  /api/trust/evaluate:
    post:
      tags: [Trust]
      summary: Evaluate sources
      description: >
        Evaluate 1 to 100 sources for a given actor, action, and channel. Returns
        a flat list of per-source decisions. Requires the `trust:evaluate` scope.
      operationId: evaluate
      security:
        - ApiKeyBearer: []
        - OAuth2ClientCredentials: [trust:evaluate]
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/EvaluateRequest' }
      responses:
        '200':
          description: Per-source decisions plus the overall (worst) decision.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/EvaluateResponse' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
  /api/trust/filter-sources:
    post:
      tags: [Trust]
      summary: Filter sources (RAG gate)
      description: >
        The RAG-shaped variant of evaluate. Same request, but the response is
        partitioned into allowed / warned / blocked so you can pass allowed (and
        warned, honoring their obligations) straight into your LLM context.
        Requires the `trust:evaluate` scope.
      operationId: filterSources
      security:
        - ApiKeyBearer: []
        - OAuth2ClientCredentials: [trust:evaluate]
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/EvaluateRequest' }
      responses:
        '200':
          description: Sources partitioned by decision.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/FilterSourcesResponse' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
  /api/trust/sources/{sourceId}:
    get:
      tags: [Trust]
      summary: Get source metadata
      description: >
        Decision-free trust metadata for one source, for badges and dashboards.
        For slash-heavy ids, use the `?sourceId=` query form instead of the path.
        Requires the `trust:read` scope.
      operationId: getSource
      security:
        - ApiKeyBearer: []
        - OAuth2ClientCredentials: [trust:read]
      parameters:
        - name: sourceId
          in: path
          required: true
          schema: { type: string }
          example: "entry:7f3a0c2b-2d3e-4a5b-9c1d-0e1f2a3b4c5d"
        - name: sourceId
          in: query
          required: false
          description: Alternative to the path segment for ids containing slashes.
          schema: { type: string }
      responses:
        '200':
          description: Resolved or unresolved source metadata.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/SourceMetadata' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
  /api/trust/usage:
    post:
      tags: [Trust]
      summary: Log usage
      description: >
        Record that an AI system actually relied on certain sources, optionally
        linked to an earlier decision. Closes the loop between "allowed" and
        "used". Requires the `trust:usage` scope.
      operationId: logUsage
      security:
        - ApiKeyBearer: []
        - OAuth2ClientCredentials: [trust:usage]
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/UsageRequest' }
      responses:
        '202':
          description: Accepted.
          content:
            application/json:
              schema:
                type: object
                properties:
                  usageId: { type: string, format: uuid }
        '401': { $ref: '#/components/responses/Unauthorized' }
  /api/trust/decisions/{decisionId}:
    get:
      tags: [Trust]
      summary: Explain a decision
      description: >
        Fetch a past decision with its per-source evidence exactly as it was at
        decision time. The audit trail. Requires the `trust:read` scope.
      operationId: getDecision
      security:
        - ApiKeyBearer: []
        - OAuth2ClientCredentials: [trust:read]
      parameters:
        - name: decisionId
          in: path
          required: true
          schema: { type: string, format: uuid }
      responses:
        '200':
          description: The full ledger record.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/DecisionRecord' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '404': { description: No decision with that id. }
components:
  securitySchemes:
    ApiKeyBearer:
      type: http
      scheme: bearer
      description: "A tenant API key, e.g. `Authorization: Bearer rk_live_...`."
    ApiKeyHeader:
      type: apiKey
      in: header
      name: X-Api-Key
      description: "The same tenant API key, e.g. `X-Api-Key: rk_live_...`."
    OAuth2ClientCredentials:
      type: oauth2
      description: Client-credentials flow against the mirrored OpenIddict client.
      flows:
        clientCredentials:
          tokenUrl: /connect/token
          scopes:
            trust:evaluate: Evaluate and filter sources.
            trust:read: Read source metadata and decisions.
            trust:usage: Log usage.
  responses:
    BadRequest:
      description: Invalid request (e.g. no sources, or more than 100).
      content:
        application/json:
          schema:
            type: object
            properties:
              error: { type: string }
    Unauthorized:
      description: Missing or invalid credentials, or insufficient scope.
  schemas:
    TrustContext:
      type: object
      description: Who is asking, for what action, over which channel.
      properties:
        actorType:
          type: string
          description: e.g. "human", "ai_agent", "service". Anything but human/user is treated as an AI actor.
          example: ai_agent
        actorLabel:
          type: string
          description: Free-text label for audit color, e.g. the bot name.
          example: support-bot
        action:
          type: string
          example: answer_customer
        channel:
          type: string
          description: e.g. "internal", "customer_facing", "public_support_chat".
          example: public_support_chat
        model:
          type: string
          example: gpt-4o-mini
        externalModel:
          type: boolean
          description: True when content would be sent to an external model provider.
          example: true
    EvaluateRequest:
      type: object
      required: [sources]
      properties:
        sources:
          type: array
          minItems: 1
          maxItems: 100
          items: { type: string }
          description: Source ids — `entry:{guid}`, `{connectorKey}:{externalId}`, or a URL.
          example: ["confluence:SPACE:12345", "entry:7f3a0c2b-2d3e-4a5b-9c1d-0e1f2a3b4c5d"]
        context: { $ref: '#/components/schemas/TrustContext' }
    Reason:
      type: object
      properties:
        code: { type: string, example: source_changed_after_review }
        dimension:
          type: string
          enum: [freshness, ownership, provenance, policy, sensitivity, context]
        severity: { $ref: '#/components/schemas/Decision' }
        message: { type: string }
        data: { type: object, additionalProperties: true, nullable: true }
    Requirements:
      type: object
      description: Obligations the caller must honor when using a warned source.
      properties:
        citationRequired: { type: boolean }
        disclaimerRequired: { type: boolean }
        disclaimerText: { type: string, nullable: true }
    Decision:
      type: string
      enum: [allow, warn, block, escalate]
    SourceDecision:
      type: object
      properties:
        sourceId: { type: string }
        entryId: { type: string, format: uuid, nullable: true }
        title: { type: string, nullable: true }
        origin: { type: string, description: "Native or Connected.", nullable: true }
        decision: { $ref: '#/components/schemas/Decision' }
        reasons:
          type: array
          items: { $ref: '#/components/schemas/Reason' }
        freshnessScore: { type: integer, nullable: true, minimum: 0, maximum: 100 }
        classification: { type: string, nullable: true }
        policySource: { type: string, nullable: true }
        recommendedAction: { type: string, nullable: true }
        requirements: { $ref: '#/components/schemas/Requirements' }
    EvaluateResponse:
      type: object
      properties:
        decisionId: { type: string, format: uuid }
        evaluatedAt: { type: string, format: date-time }
        overallDecision: { $ref: '#/components/schemas/Decision' }
        results:
          type: array
          items: { $ref: '#/components/schemas/SourceDecision' }
    FilterSourcesResponse:
      type: object
      properties:
        decisionId: { type: string, format: uuid }
        evaluatedAt: { type: string, format: date-time }
        allowed:
          type: array
          items: { $ref: '#/components/schemas/SourceDecision' }
        warned:
          type: array
          items: { $ref: '#/components/schemas/SourceDecision' }
        blocked:
          type: array
          items: { $ref: '#/components/schemas/SourceDecision' }
    SourceMetadata:
      type: object
      properties:
        sourceId: { type: string }
        resolved: { type: boolean }
        entryId: { type: string, format: uuid, nullable: true }
        title: { type: string, nullable: true }
        origin: { type: string, nullable: true }
        status: { type: string, nullable: true }
        version: { type: integer, nullable: true }
        externalUrl: { type: string, nullable: true }
        contentUpdatedAt: { type: string, format: date-time, nullable: true }
        policy:
          type: object
          nullable: true
          properties:
            source: { type: string }
            aiRetrievable: { type: boolean }
            customerFacingAllowed: { type: boolean }
            internalOnly: { type: boolean }
            externalModelAllowed: { type: boolean }
            maxSensitivityForAi: { type: string }
    UsageRequest:
      type: object
      properties:
        decisionId: { type: string, format: uuid, nullable: true }
        actorLabel: { type: string, nullable: true }
        action: { type: string, nullable: true }
        sources:
          type: array
          items: { type: string }
        outcome: { type: string, nullable: true, example: answer_sent }
    DecisionRecord:
      type: object
      properties:
        decisionId: { type: string, format: uuid }
        evaluatedAt: { type: string, format: date-time }
        overallDecision: { $ref: '#/components/schemas/Decision' }
        actorKind: { type: string, enum: [user, machine, system] }
        actorLabel: { type: string, nullable: true }
        action: { type: string, nullable: true }
        channel: { type: string, nullable: true }
        endpoint: { type: string, description: "Evaluate or FilterSources." }
        counts:
          type: object
          properties:
            total: { type: integer }
            allowed: { type: integer }
            warned: { type: integer }
            blocked: { type: integer }
            escalated: { type: integer }
        sources:
          type: array
          items: { $ref: '#/components/schemas/SourceDecision' }
