openapi: 3.0.3
info:
  title: naturali.ai — Knowledge API
  version: 1.0.0
  description: >
    Knowledge, scoped to a project (API.md §4 — Knowledge). A **collection** is
    the named, project-scoped library an agent works from; a **document** is one
    ingested file inside it. A collection is a naturali-side grouping record
    (there is no runtime collection object) that maps to runtime documents filed
    under the collection's path prefix; a document maps to one backing runtime
    document, whose content, ingestion status and chunks live on the runtime
    (the source of truth) and are read from there when shaping responses.


    A document is created either from inline text or from an uploaded **file**
    (`file` + `content_type`) — PDFs and text/markdown are extracted natively,
    and any other media type is ingested through a **converter** the project
    registers for it (`/knowledge/converters`), which is how images (OCR) and
    audio (transcription) become searchable text. Collection versioning is a
    deliberate follow-up (API.md §4, K2–K4).
  contact:
    name: naturali.ai
    url: https://naturali.ai
servers:
  - url: '{baseUrl}'
    description: Host of your naturali.ai deployment; every path carries the /v1 prefix.
    variables:
      baseUrl:
        description: Base host URL.
        default: https://api.naturali.ai
tags:
  - name: Knowledge
    description: Create and manage a project's knowledge collections and documents.
security:
  - bearerAuth: []
paths:
  /v1/projects/{project_id}/knowledge/collections:
    parameters:
      - $ref: '#/components/parameters/ProjectId'
    get:
      tags: [Knowledge]
      summary: List collections
      description: Lists the knowledge collections in the project.
      operationId: listKnowledgeCollections
      parameters:
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/Cursor'
      responses:
        '200':
          description: A page of collections.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/KnowledgeCollectionList'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
    post:
      tags: [Knowledge]
      summary: Create a collection
      description: >
        Create a knowledge collection. The name is the key manifests reference
        (an agent's `knowledge:` block) and must be unique within the project.
      operationId: createKnowledgeCollection
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/KnowledgeCollectionCreate'
      responses:
        '201':
          description: Collection created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/KnowledgeCollection'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
  /v1/projects/{project_id}/knowledge/collections/{collection_id}:
    parameters:
      - $ref: '#/components/parameters/ProjectId'
      - $ref: '#/components/parameters/CollectionId'
    get:
      tags: [Knowledge]
      summary: Get a collection
      operationId: getKnowledgeCollection
      responses:
        '200':
          description: Collection details.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/KnowledgeCollection'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
    patch:
      tags: [Knowledge]
      summary: Update a collection
      description: Rename the collection or edit its description. At least one field is required.
      operationId: updateKnowledgeCollection
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/KnowledgeCollectionUpdate'
      responses:
        '200':
          description: Collection updated.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/KnowledgeCollection'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
    delete:
      tags: [Knowledge]
      summary: Delete a collection
      description: >
        Deletes an empty collection. Returns 409 if the collection still has
        documents (delete them first).
      operationId: deleteKnowledgeCollection
      responses:
        '204':
          description: Collection deleted.
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
  '/v1/projects/{project_id}/knowledge/collections/{collection_id}:query':
    parameters:
      - $ref: '#/components/parameters/ProjectId'
      - $ref: '#/components/parameters/CollectionId'
    post:
      tags: [Knowledge]
      summary: Query a collection (retrieval preview)
      description: >
        Retrieval preview (API.md §4, K5): returns the chunks the collection
        would surface for a question — debuggable standalone, before any agent is
        bound to it. This is the `…:query` action; the path segment is
        `{collection_id}:query`.
      operationId: queryKnowledgeCollection
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/KnowledgeQueryRequest'
      responses:
        '200':
          description: The chunks that matched, most relevant first.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/KnowledgeQueryResult'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '502':
          $ref: '#/components/responses/UpstreamUnavailable'
  /v1/projects/{project_id}/knowledge/collections/{collection_id}/documents:
    parameters:
      - $ref: '#/components/parameters/ProjectId'
      - $ref: '#/components/parameters/CollectionId'
    get:
      tags: [Knowledge]
      summary: List documents
      description: Lists the documents in the collection, with their ingestion status.
      operationId: listKnowledgeDocuments
      parameters:
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/Cursor'
      responses:
        '200':
          description: A page of documents.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/KnowledgeDocumentList'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '502':
          $ref: '#/components/responses/UpstreamUnavailable'
    post:
      tags: [Knowledge]
      summary: Create a document
      description: >
        Add a document to the collection, from **inline text** (`content`) or
        from an **uploaded file** (`file`, base64, plus `content_type` and
        `filename`) — exactly one of the two.


        `application/pdf`, `text/plain` and `text/markdown` are extracted
        natively. Any other media type needs a
        converter
        (`POST /v1/projects/{project_id}/knowledge/converters`) registered for it in the project; without one the request is rejected
        with `unsupported_content_type` and no document is created.


        Ingestion (extract → chunk → embed) runs in the background: the document
        comes back `pending` and becomes `indexed` or `failed`, which is
        announced by the `knowledge.document_ingested` /
        `knowledge.ingest_failed` webhook events.
      operationId: createKnowledgeDocument
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/KnowledgeDocumentCreate'
      responses:
        '201':
          description: Document created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/KnowledgeDocument'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '413':
          $ref: '#/components/responses/PayloadTooLarge'
        '502':
          $ref: '#/components/responses/UpstreamUnavailable'
  /v1/projects/{project_id}/knowledge/collections/{collection_id}/documents/{document_id}:
    parameters:
      - $ref: '#/components/parameters/ProjectId'
      - $ref: '#/components/parameters/CollectionId'
      - $ref: '#/components/parameters/DocumentId'
    get:
      tags: [Knowledge]
      summary: Get a document
      description: Returns the document, including its text content when ingestion is complete.
      operationId: getKnowledgeDocument
      responses:
        '200':
          description: Document details.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/KnowledgeDocument'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '502':
          $ref: '#/components/responses/UpstreamUnavailable'
    delete:
      tags: [Knowledge]
      summary: Delete a document
      operationId: deleteKnowledgeDocument
      responses:
        '204':
          description: Document deleted.
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '502':
          $ref: '#/components/responses/UpstreamUnavailable'
  '/v1/projects/{project_id}/knowledge/collections/{collection_id}/documents/{document_id}:reingest':
    parameters:
      - $ref: '#/components/parameters/ProjectId'
      - $ref: '#/components/parameters/CollectionId'
      - $ref: '#/components/parameters/DocumentId'
    post:
      tags: [Knowledge]
      summary: Re-ingest a document
      description: >
        Re-run ingestion for a document against its stored source, resetting it
        to `pending` before re-processing — the recovery path for a `failed`
        ingest (API.md §4, K1). This is the `…:reingest` action; the path segment
        is `{document_id}:reingest`.
      operationId: reingestKnowledgeDocument
      responses:
        '202':
          description: Re-ingestion accepted; poll the document for status.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/KnowledgeDocument'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '502':
          $ref: '#/components/responses/UpstreamUnavailable'
  /v1/projects/{project_id}/knowledge/converters:
    parameters:
      - $ref: '#/components/parameters/ProjectId'
    get:
      tags: [Knowledge]
      summary: List converters
      description: Lists the media converters registered in the project.
      operationId: listKnowledgeConverters
      parameters:
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/Cursor'
      responses:
        '200':
          description: A page of converters.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/KnowledgeConverterList'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '502':
          $ref: '#/components/responses/UpstreamUnavailable'
    post:
      tags: [Knowledge]
      summary: Create a converter
      description: >
        Register a converter for a media type the platform cannot extract
        natively, so files of that type become ingestable documents like any
        other. A converter maps a `content_type` glob (`image/*`,
        `audio/mpeg`, …) onto one of two workers:


        - an **agent** (`agent_id`) — the file is handed to a
          multimodal model with a fixed "extract all the text" instruction and
          its answer becomes the document text. The shortest path for images
          and scanned PDFs; nothing to map.

        - a **tool** (`tool_id`) — the file is passed to an
          `http` tool as `{ content_type, filename, data_base64 }`, and
          whatever string the tool returns becomes the document text. The path
          for dedicated non-chat APIs (speech-to-text, a specialist OCR
          engine); use the tool's `execute.body_mode: multipart` for
          form-data endpoints and its `output_mapping` to reduce a JSON
          response to the bare string.


        Exactly one of `agent_id` / `tool_id`, and one converter per
        `content_type` in a project.
      operationId: createKnowledgeConverter
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/KnowledgeConverterCreate'
      responses:
        '201':
          description: Converter created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/KnowledgeConverter'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '502':
          $ref: '#/components/responses/UpstreamUnavailable'
  /v1/projects/{project_id}/knowledge/converters/{converter_id}:
    parameters:
      - $ref: '#/components/parameters/ProjectId'
      - $ref: '#/components/parameters/ConverterId'
    get:
      tags: [Knowledge]
      summary: Get a converter
      operationId: getKnowledgeConverter
      responses:
        '200':
          description: Converter details.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/KnowledgeConverter'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '502':
          $ref: '#/components/responses/UpstreamUnavailable'
    patch:
      tags: [Knowledge]
      summary: Update a converter
      description: >
        Change the worker or the chunking defaults. At least one field is
        required; `agent_id` and `tool_id` stay mutually exclusive, so setting
        one clears the other.
      operationId: updateKnowledgeConverter
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/KnowledgeConverterUpdate'
      responses:
        '200':
          description: Converter updated.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/KnowledgeConverter'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '502':
          $ref: '#/components/responses/UpstreamUnavailable'
    delete:
      tags: [Knowledge]
      summary: Delete a converter
      description: >
        Removes the converter. Documents already ingested through it are
        untouched; new files of that media type stop being ingestable until
        another converter covers them.
      operationId: deleteKnowledgeConverter
      responses:
        '204':
          description: Converter deleted.
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '502':
          $ref: '#/components/responses/UpstreamUnavailable'
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: A naturali API key (nat_sk_…) or a session JWT.
  parameters:
    Limit:
      name: limit
      in: query
      required: false
      description: Maximum items per page — an integer from 1 to 100 (default 20).
      schema:
        type: integer
        minimum: 1
        maximum: 100
        default: 20
    Cursor:
      name: cursor
      in: query
      required: false
      description: Opaque pagination cursor from a previous response's next_cursor.
      schema:
        type: string
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: false
      description: Client-supplied key to make this mutating POST idempotent.
      schema:
        type: string
    ProjectId:
      name: project_id
      in: path
      required: true
      description: Project public ID (proj_ prefix).
      schema:
        type: string
        example: proj_V1StGXR8Z5jdHi6B
    CollectionId:
      name: collection_id
      in: path
      required: true
      description: Knowledge collection public ID (kcol_ prefix).
      schema:
        type: string
        example: kcol_V1StGXR8Z5jdHi6B
    DocumentId:
      name: document_id
      in: path
      required: true
      description: Knowledge document public ID (doc_ prefix) — the runtime document id.
      schema:
        type: string
        example: doc_V1StGXR8Z5jdHi6B
    ConverterId:
      name: converter_id
      in: path
      required: true
      description: Knowledge converter public ID (igr_ prefix).
      schema:
        type: string
        example: igr_V1StGXR8Z5jdHi6B
  responses:
    Unauthorized:
      description: Missing or invalid credentials.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    BadRequest:
      description: The request was malformed or failed validation.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    NotFound:
      description: The resource does not exist (existence is not leaked).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    Conflict:
      description: The request conflicts with the resource's current state.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    PayloadTooLarge:
      description: The uploaded file exceeds the maximum size.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    UpstreamUnavailable:
      description: The upstream runtime could not complete the operation.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
  schemas:
    KnowledgeCollection:
      type: object
      properties:
        id:
          type: string
          description: Public collection ID (kcol_ prefix).
          example: kcol_V1StGXR8Z5jdHi6B
        project_id:
          type: string
          example: proj_V1StGXR8Z5jdHi6B
        name:
          type: string
          description: Unique within the project; the name manifests reference.
          example: support-playbooks
        description:
          type: string
          nullable: true
          example: Runbooks and FAQ the support agent works from.
        document_count:
          type: integer
          description: Number of documents currently in the collection.
          example: 3
        created_at:
          type: string
          format: date-time
          example: '2026-07-18T00:00:00.000Z'
        updated_at:
          type: string
          format: date-time
          example: '2026-07-18T00:00:00.000Z'
      required:
        - id
        - project_id
        - name
        - description
        - document_count
        - created_at
        - updated_at
    KnowledgeCollectionCreate:
      type: object
      required:
        - name
      properties:
        name:
          type: string
          description: Unique within the project.
          example: support-playbooks
        description:
          type: string
          example: Runbooks and FAQ the support agent works from.
    KnowledgeCollectionUpdate:
      type: object
      description: At least one field must be present.
      minProperties: 1
      properties:
        name:
          type: string
          example: support-playbooks-v2
        description:
          type: string
          nullable: true
          example: Updated description.
    KnowledgeCollectionList:
      type: object
      required: [data, next_cursor]
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/KnowledgeCollection'
        next_cursor:
          type: string
          nullable: true
          description: Cursor for the next page, or null at the end.
          example: null
    KnowledgeDocument:
      type: object
      properties:
        id:
          type: string
          description: Public document ID (doc_ prefix) — the runtime document id.
          example: doc_V1StGXR8Z5jdHi6B
        collection_id:
          type: string
          example: kcol_V1StGXR8Z5jdHi6B
        project_id:
          type: string
          example: proj_V1StGXR8Z5jdHi6B
        filename:
          type: string
          nullable: true
          example: refunds.md
        content_type:
          type: string
          nullable: true
          description: >
            Media type of the source file; null for an inline-text document.
          example: application/pdf
        status:
          type: string
          description: >
            Ingestion status (API.md §4, K1). `pending` — enqueued/processing;
            `indexed` — fully embedded and retrievable; `failed` — ingestion
            error (see `error`).
          enum: [pending, indexed, failed]
          example: indexed
        error:
          type: string
          nullable: true
          description: Failure reason when status is `failed`; null otherwise.
          example: null
        size:
          type: integer
          nullable: true
          description: Source size in bytes.
          example: 1024
        chunk_count:
          type: integer
          nullable: true
          description: >
            Number of embedded chunks the document was split into; null until
            ingestion finishes.
          example: 4
        content:
          type: string
          nullable: true
          description: Text content — present on get when indexed, null on list.
          example: To issue a refund, open the order and…
        created_at:
          type: string
          format: date-time
          example: '2026-07-18T00:00:00.000Z'
        updated_at:
          type: string
          format: date-time
          example: '2026-07-18T00:00:00.000Z'
      required:
        - id
        - collection_id
        - project_id
        - filename
        - content_type
        - status
        - error
        - size
        - chunk_count
        - content
        - created_at
        - updated_at
    KnowledgeDocumentCreate:
      type: object
      description: >
        Exactly one of `content` (inline text) or `file` (a base64-encoded
        upload). `file` additionally requires `content_type` and `filename`.
      properties:
        content:
          type: string
          description: The document's text content. Mutually exclusive with `file`.
          example: To issue a refund, open the order and…
        file:
          type: string
          format: byte
          description: >
            The file's bytes, base64-encoded. Mutually exclusive with
            `content`.
          example: JVBERi0xLjQKMSAwIG9iago8PCAvVHlwZQ==
        content_type:
          type: string
          description: >
            Media type of `file`. `application/pdf`, `text/plain` and
            `text/markdown` are extracted natively; anything else needs a
            matching converter in the project. Required with `file`.
          example: application/pdf
        filename:
          type: string
          description: >
            A label for the document; also its logical filename. Required with
            `file`.
          example: printer-x1000.pdf
        title:
          type: string
          description: Human-readable title.
          example: Refund policy
        chunk_strategy:
          type: string
          description: >
            How the extracted text is split for embedding. `page` (the default)
            makes one chunk per page, which is what lets a retrieved chunk cite
            a page number; `size` makes fixed-width character windows, which
            retrieves more sharply on dense pages at the cost of that citation;
            `whole` keeps the document as a single chunk. Ignored for inline
            text.
          enum: [page, size, whole]
          default: page
          example: size
        chunk_size:
          type: integer
          minimum: 100
          maximum: 8000
          default: 1000
          description: Window width in characters, when `chunk_strategy` is `size`.
          example: 1000
        chunk_overlap:
          type: integer
          minimum: 0
          maximum: 4000
          default: 200
          description: >
            Characters of overlap between consecutive windows, when
            `chunk_strategy` is `size`. Must be smaller than `chunk_size`.
          example: 200
    KnowledgeDocumentList:
      type: object
      required: [data, next_cursor]
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/KnowledgeDocument'
        next_cursor:
          type: string
          nullable: true
          description: Cursor for the next page, or null at the end.
          example: null
    KnowledgeConverter:
      type: object
      properties:
        id:
          type: string
          description: Public converter ID (igr_ prefix).
          example: igr_V1StGXR8Z5jdHi6B
        project_id:
          type: string
          example: proj_V1StGXR8Z5jdHi6B
        content_type:
          type: string
          description: >
            The media-type glob this converter claims, matched against an
            uploaded file's `content_type`.
          example: image/*
        agent_id:
          type: string
          nullable: true
          description: The agent that converts the file; null for a tool converter.
          example: agent_V1StGXR8Z5jdHi6B
        tool_id:
          type: string
          nullable: true
          description: The tool that converts the file; null for an agent converter.
          example: null
        preset_parameters:
          type: object
          additionalProperties: true
          nullable: true
          description: >
            Fixed arguments merged into every call to a tool converter (e.g.
            `{ "language": "en" }`). Null for an agent converter.
          example:
            language: en
        native_extraction:
          type: string
          description: >
            What to do for a media type the platform *can* extract natively
            (PDF, text, markdown). `first` — try native extraction and fall
            back to the converter only when it yields no text, which is what
            makes a converter a scanned-PDF fallback; `skip` — always convert.
          enum: [first, skip]
          example: first
        chunk_strategy:
          type: string
          nullable: true
          description: >
            Default chunking for documents this converter produces, overridable
            per document at create time.
          enum: [page, size, whole]
          example: whole
        chunk_size:
          type: integer
          nullable: true
          example: 1000
        chunk_overlap:
          type: integer
          nullable: true
          example: 200
        created_at:
          type: string
          format: date-time
          example: '2026-07-18T00:00:00.000Z'
        updated_at:
          type: string
          format: date-time
          example: '2026-07-18T00:00:00.000Z'
      required:
        - id
        - project_id
        - content_type
        - agent_id
        - tool_id
        - preset_parameters
        - native_extraction
        - chunk_strategy
        - chunk_size
        - chunk_overlap
        - created_at
        - updated_at
    KnowledgeConverterCreate:
      type: object
      description: Exactly one of `agent_id` or `tool_id` is required.
      required:
        - content_type
      properties:
        content_type:
          type: string
          description: >
            The media-type glob to claim — an exact type (`audio/mpeg`) or a
            `*` wildcard on the subtype (`image/*`).
          example: image/*
        agent_id:
          type: string
          description: An agent in this project, used as the converter.
          example: agent_V1StGXR8Z5jdHi6B
        tool_id:
          type: string
          description: An `http` tool in this project, used as the converter.
          example: tool_V1StGXR8Z5jdHi6B
        preset_parameters:
          type: object
          additionalProperties: true
          description: Fixed arguments merged into every tool-converter call.
          example:
            language: en
        native_extraction:
          type: string
          enum: [first, skip]
          default: first
          example: first
        chunk_strategy:
          type: string
          enum: [page, size, whole]
          example: whole
        chunk_size:
          type: integer
          minimum: 100
          maximum: 8000
          example: 1000
        chunk_overlap:
          type: integer
          minimum: 0
          maximum: 4000
          example: 200
    KnowledgeConverterUpdate:
      type: object
      description: At least one field must be present.
      minProperties: 1
      properties:
        content_type:
          type: string
          example: image/png
        agent_id:
          type: string
          description: Switches the converter to this agent, clearing `tool_id`.
          example: agent_V1StGXR8Z5jdHi6B
        tool_id:
          type: string
          description: Switches the converter to this tool, clearing `agent_id`.
          example: tool_V1StGXR8Z5jdHi6B
        preset_parameters:
          type: object
          additionalProperties: true
          nullable: true
          example:
            language: pt
        native_extraction:
          type: string
          enum: [first, skip]
          example: skip
        chunk_strategy:
          type: string
          enum: [page, size, whole]
          example: size
        chunk_size:
          type: integer
          minimum: 100
          maximum: 8000
          example: 1000
        chunk_overlap:
          type: integer
          minimum: 0
          maximum: 4000
          example: 200
    KnowledgeConverterList:
      type: object
      required: [data, next_cursor]
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/KnowledgeConverter'
        next_cursor:
          type: string
          nullable: true
          description: Cursor for the next page, or null at the end.
          example: null
    KnowledgeQueryRequest:
      type: object
      required:
        - query
      properties:
        query:
          type: string
          description: The question to preview retrieval for.
          example: How do I issue a refund?
        top_k:
          type: integer
          minimum: 1
          maximum: 50
          default: 10
          description: Maximum chunks to return.
          example: 5
        min_score:
          type: number
          minimum: 0
          maximum: 1
          description: Drop chunks below this similarity score.
          example: 0.5
    KnowledgeQueryResult:
      type: object
      required: [data]
      properties:
        data:
          type: array
          description: The matched chunks, most relevant first.
          items:
            $ref: '#/components/schemas/KnowledgeChunk'
    KnowledgeChunk:
      type: object
      properties:
        document_id:
          type: string
          description: The document this chunk belongs to.
          example: doc_V1StGXR8Z5jdHi6B
        chunk_id:
          type: string
          nullable: true
          description: The matching chunk's id.
          example: chnk_V1StGXR8Z5jdHi6B
        score:
          type: number
          nullable: true
          description: Semantic similarity (0–1); null when not scored.
          example: 0.82
        content:
          type: string
          nullable: true
          description: The chunk text.
          example: To issue a refund, open the order and…
      required:
        - document_id
        - chunk_id
        - score
        - content
    ErrorResponse:
      type: object
      required: [error]
      properties:
        error:
          type: object
          required: [code, message]
          properties:
            code:
              type: string
              example: upstream_unavailable
            message:
              type: string
              example: Could not query the collection on the upstream runtime.
            details:
              type: object
              additionalProperties: true
              description: Optional structured context for the error.
