openapi: 3.0.3
info:
  title: SOAT Knowledge API
  version: 1.0.0
  description: Unified semantic search across knowledge sources
  contact:
    name: SOAT Team
    url: https://github.com/ttoss/soat
servers:
  - url: '{baseUrl}'
    description: Base URL of your SOAT deployment (e.g. https://your-soat.com or http://localhost:5047)
    variables:
      baseUrl:
        description: The base URL of your SOAT deployment
        default: http://localhost:5047
tags:
  - name: Knowledge
    description: Unified search across documents and knowledge sources
security:
  - bearerAuth: []
paths:
  /api/v1/knowledge/search:
    post:
      tags:
        - Knowledge
      summary: Search knowledge
      description: Searches across documents and memory entries using semantic search, file paths, document IDs, or memory IDs/tags. At least one of `query`, `document_paths`, `document_ids`, `memory_ids`, or `memory_tags` must be provided.
      operationId: searchKnowledge
      x-iam-action: knowledge:SearchKnowledge
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                project_id:
                  x-soat-ref: projects
                  type: string
                  description: Limit search to a specific project
                  example: 'proj_V1StGXR8Z5jdHi6B'
                query:
                  type: string
                  description: Semantic search query text
                  example: 'customer communication preferences'
                min_score:
                  type: number
                  description: >-
                    Minimum `score` a result must reach to be returned. Filters on the
                    implementation-defined `score`, not on `similarity_score`, so the
                    cutoff follows the ranking. Only applies when `query` is provided.
                    Because the scale behind `score` is not part of the contract, treat a
                    tuned value as tied to the deployment rather than portable.
                  minimum: 0
                  maximum: 1
                  example: 0.5
                limit:
                  type: integer
                  description: Maximum number of results to return (default 10)
                  minimum: 1
                  example: 10
                memory_ids:
                  x-soat-ref: memories
                  type: array
                  description: Search entries within these specific memories
                  items:
                    type: string
                  example: ['mem_V1StGXR8Z5jdHi6B']
                memory_tags:
                  type: array
                  description: Search entries in memories whose tags match any of these patterns (glob supported)
                  items:
                    type: string
                  example: ['customer*', 'support']
                document_paths:
                  type: array
                  description: Filter results to documents whose file path starts with one of these prefixes
                  items:
                    type: string
                  example: ['/sales/', '/hr/']
                document_ids:
                  x-soat-ref: documents
                  type: array
                  description: Filter results to specific document IDs
                  items:
                    type: string
                  example: ['doc_V1StGXR8Z5jdHi6B']
      responses:
        '200':
          description: Search results
          content:
            application/json:
              schema:
                type: object
                required:
                  - results
                properties:
                  results:
                    type: array
                    items:
                      $ref: '#/components/schemas/KnowledgeResult'
        '400':
          description: Bad request — at least one search parameter is required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
  schemas:
    KnowledgeResult:
      oneOf:
        - $ref: '#/components/schemas/DocumentKnowledgeResult'
        - $ref: '#/components/schemas/MemoryKnowledgeResult'
      discriminator:
        propertyName: source_type
        mapping:
          document: '#/components/schemas/DocumentKnowledgeResult'
          memory: '#/components/schemas/MemoryKnowledgeResult'
    DocumentKnowledgeResult:
      type: object
      required:
        - source_type
        - document_id
        - content
        - created_at
        - updated_at
      properties:
        source_type:
          type: string
          enum:
            - document
          description: The type of knowledge source this result comes from
          example: document
        document_id:
          x-soat-ref: documents
          type: string
          description: Public ID of the document
          example: 'doc_V1StGXR8Z5jdHi6B'
        chunk_id:
          type: string
          description: Public ID of the document chunk that matched the query
          example: 'dchunk_V1StGXR8Z5jdHi6B'
        page:
          type: integer
          nullable: true
          description: Page number within the source PDF (1-indexed). Null for plain-text documents.
          example: 3
        file_id:
          x-soat-ref: files
          type: string
          description: Public ID of the underlying file
          example: 'file_V1StGXR8Z5jdHi6B'
        project_id:
          x-soat-ref: projects
          type: string
          description: Public ID of the project the document belongs to
          example: 'proj_V1StGXR8Z5jdHi6B'
        path:
          type: string
          description: Logical path of the file within the project
          example: '/sales/policies.txt'
        filename:
          type: string
          description: Filename of the underlying file
          example: 'policies.txt'
        size:
          type: integer
          description: File size in bytes
          example: 1024
        title:
          type: string
          description: Document title
          example: 'Sales Communication Policy'
        metadata:
          type: object
          description: >-
            Arbitrary metadata attached to the document, returned verbatim in
            the casing it was written with at create/update time (e.g. a key
            written as `strapiDocumentId` is returned as `strapiDocumentId`,
            not `strapi_document_id`) — it is not converted between
            snake_case and camelCase like other response fields.
        tags:
          type: object
          additionalProperties:
            type: string
          description: Key-value tags
          example:
            department: sales
        content:
          type: string
          nullable: true
          description: Full text content of the document
        score:
          type: number
          description: >-
            Implementation-defined relevance ranking — higher is better. The **ordering**
            it produces is the contract; the absolute value is not, and the formula behind
            it may change (a future hybrid ranking would fuse several signals here). It is
            the field `min_score` filters on and the field results are sorted by. Only
            present when `query` was provided. Use `similarity_score` when you need the raw
            cosine value.
          example: 0.82
        similarity_score:
          type: number
          description: Raw cosine similarity (0–1) between the query and this result. Pinned to that meaning — unlike `score`, it is never redefined. Only present when `query` was provided.
          minimum: 0
          maximum: 1
          example: 0.82
        created_at:
          type: string
          format: date-time
          description: Creation timestamp
        updated_at:
          type: string
          format: date-time
          description: Last updated timestamp
    MemoryKnowledgeResult:
      type: object
      required:
        - source_type
        - entry_id
        - memory_id
        - memory_name
        - content
        - created_at
        - updated_at
      properties:
        source_type:
          type: string
          enum:
            - memory
          description: The type of knowledge source this result comes from
          example: memory
        entry_id:
          type: string
          description: Public ID of the memory entry
          example: 'mem_entry_V1StGXR8Z5jdHi6B'
        memory_id:
          x-soat-ref: memories
          type: string
          description: Public ID of the parent memory
          example: 'mem_V1StGXR8Z5jdHi6B'
        memory_name:
          type: string
          description: Human-readable name of the parent memory
          example: 'Customer Preferences'
        content:
          type: string
          description: Text content of the memory entry
        score:
          type: number
          description: >-
            Implementation-defined relevance ranking — higher is better. The **ordering**
            it produces is the contract; the absolute value is not, and the formula behind
            it may change (a future hybrid ranking would fuse several signals here). It is
            the field `min_score` filters on and the field results are sorted by. Only
            present when `query` was provided. Use `similarity_score` when you need the raw
            cosine value.
          example: 0.79
        similarity_score:
          type: number
          description: Raw cosine similarity (0–1) between the query and this result. Pinned to that meaning — unlike `score`, it is never redefined. Only present when `query` was provided.
          minimum: 0
          maximum: 1
          example: 0.79
        created_at:
          type: string
          format: date-time
          description: Creation timestamp
        updated_at:
          type: string
          format: date-time
          description: Last updated timestamp
    ErrorResponse:
      type: object
      required:
        - error
      properties:
        error:
          type: object
          description: >-
            Structured error. Every error response uses this shape — 401, 403
            and the 500 catch-all included — so `code` can be read without
            first checking the type of `error`.
          required:
            - code
            - message
            - hint
            - docs_url
          properties:
            code:
              type: string
              description: A key from the server's ERROR_CODES registry.
              example: RESOURCE_NOT_FOUND
            message:
              type: string
              example: 'Resource not found'
            hint:
              type: string
              description: >-
                What to do about this error. Resolved per code, so a caller that
                has never seen the code before can act on the response without
                leaving it.
              example: >-
                Check the id, and check that the credential can see the project
                that owns the resource.
            docs_url:
              type: string
              format: uri
              description: The reference-page anchor documenting this code.
              example: >-
                https://soat.ttoss.dev/docs/error-codes#resource_not_found
            meta:
              type: object
              description: Optional structured context for the error.
