# AUTO-GENERATED tier-A mirror spec — do not edit.
# Source: openapi/runtime/conversations.yaml (runtime 0.71.2)
#  + policy: openapi/mirror-policies/conversations.json
# Regenerate: node scripts/generate-mirror-specs.mjs
openapi: 3.0.3
info:
  title: naturali.ai — Conversations API
  version: 1.0.0
  description: >-
    Conversations: the durable message threads a session records into, and the transcript reads and
    writes over them. A fully runtime-backed module — this spec is generated verbatim from the
    runtime's own, re-rooted under /v1/projects/{project_id}. The project in the path is authorized
    by naturali and enforced upstream by the project's scoped credential.


    This module mirrors the upstream runtime verbatim (tier A, #304): paths are the runtime's own
    re-rooted under /v1/projects/{project_id}, and every field, method, status code and error shape
    passes through unchanged. Errors raised by the runtime arrive in its envelope; errors raised by
    naturali itself (authentication, project resolution, an unreachable runtime) use naturali's.
  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: Conversations
    description: Manage conversations
security:
  - bearerAuth: []
  - oauth2:
      - mcp:access
paths:
  /v1/projects/{project_id}/conversations:
    get:
      tags:
        - Conversations
      summary: List conversations
      description: Returns all conversations in the project named in the path.
      operationId: listConversations
      parameters:
        - name: actor_id
          in: query
          required: false
          description: Filter by actor ID
          schema:
            type: string
            example: actor_V1StGXR8Z5jdHi6B
        - $ref: "#/components/parameters/TagsQuery"
        - name: limit
          in: query
          required: false
          description: Maximum number of results to return
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 50
        - name: offset
          in: query
          required: false
          description: Number of results to skip
          schema:
            type: integer
            default: 0
      responses:
        "200":
          description: List of conversations
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/ConversationRecord"
                  total:
                    type: integer
                  limit:
                    type: integer
                  offset:
                    type: integer
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    post:
      tags:
        - Conversations
      summary: Create a conversation
      description: Creates a new conversation in the project named in the path.
      operationId: createConversation
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                status:
                  type: string
                  enum:
                    - open
                    - closed
                  default: open
                  description: Initial conversation status
                name:
                  type: string
                  nullable: true
                  description: Optional name for the conversation
                actor_id:
                  x-naturali-ref: actors
                  type: string
                  nullable: true
                  description: Actor ID to associate with this conversation
                  example: actor_V1StGXR8Z5jdHi6B
                retrieval:
                  type: string
                  enum:
                    - embed
                    - none
                    - null
                  nullable: true
                  description: Whether this conversation's turns are embedded for vector retrieval. Turns are stored
                    and chunked either way, so `none` leaves them readable and reachable by
                    full-text search without paying for an embedding. `null` (the default) inherits
                    the project's `default_conversation_retrieval`.
      responses:
        "201":
          description: Conversation created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ConversationRecord"
        "400":
          description: Invalid request body
          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"
    parameters:
      - $ref: "#/components/parameters/ProjectId"
  /v1/projects/{project_id}/conversations/{conversation_id}:
    get:
      tags:
        - Conversations
      summary: Get a conversation by ID
      description: Returns a conversation by its ID
      operationId: getConversation
      x-naturali-resource:
        kind: conversation
        from: conversation_id
      parameters:
        - name: conversation_id
          in: path
          required: true
          description: Conversation ID
          schema:
            type: string
            example: conv_V1StGXR8Z5jdHi6B
      responses:
        "200":
          description: Conversation found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ConversationRecord"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Conversation not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    patch:
      tags:
        - Conversations
      summary: Update a conversation
      description: Updates the status of a conversation
      operationId: updateConversation
      x-naturali-resource:
        kind: conversation
        from: conversation_id
      parameters:
        - name: conversation_id
          in: path
          required: true
          description: Conversation ID
          schema:
            type: string
            example: conv_V1StGXR8Z5jdHi6B
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                status:
                  type: string
                  enum:
                    - open
                    - closed
                  description: New conversation status
                name:
                  type: string
                  nullable: true
                  description: New conversation name
                retrieval:
                  type: string
                  enum:
                    - embed
                    - none
                    - null
                  nullable: true
                  description: Whether this conversation's turns are embedded for vector retrieval. Turns are stored
                    and chunked either way, so `none` leaves them readable and reachable by
                    full-text search without paying for an embedding. `null` (the default) inherits
                    the project's `default_conversation_retrieval`. Switching it on embeds the turns
                    already in the conversation, so the whole conversation becomes retrievable
                    rather than only what is said next.
      responses:
        "200":
          description: Conversation updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ConversationRecord"
        "400":
          description: Invalid request body
          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"
        "404":
          description: Conversation not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    delete:
      tags:
        - Conversations
      summary: Delete a conversation
      description: Deletes a conversation by its ID
      operationId: deleteConversation
      x-naturali-resource:
        kind: conversation
        from: conversation_id
      parameters:
        - name: conversation_id
          in: path
          required: true
          description: Conversation ID
          schema:
            type: string
            example: conv_V1StGXR8Z5jdHi6B
      responses:
        "204":
          description: Conversation deleted
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Conversation not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    parameters:
      - $ref: "#/components/parameters/ProjectId"
  /v1/projects/{project_id}/conversations/{conversation_id}/messages:
    get:
      tags:
        - Conversations
      summary: List conversation messages
      description: Returns all messages (documents) attached to a conversation, ordered by position
      operationId: listConversationMessages
      x-naturali-resource:
        kind: conversation
        from: conversation_id
      parameters:
        - name: conversation_id
          in: path
          required: true
          description: Conversation ID
          schema:
            type: string
            example: conv_V1StGXR8Z5jdHi6B
        - name: limit
          in: query
          required: false
          description: Maximum number of results to return
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 50
        - name: offset
          in: query
          required: false
          description: Number of results to skip
          schema:
            type: integer
            default: 0
      responses:
        "200":
          description: List of messages
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/ConversationMessageRecord"
                  total:
                    type: integer
                  limit:
                    type: integer
                  offset:
                    type: integer
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Conversation not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    post:
      tags:
        - Conversations
      summary: Add a message to a conversation
      description: Creates a document from the message text and attaches it to the conversation at the
        given position. If position is omitted, it is appended at the end.
      operationId: addConversationMessage
      x-naturali-resource:
        kind: conversation
        from: conversation_id
      parameters:
        - name: conversation_id
          in: path
          required: true
          description: Conversation ID
          schema:
            type: string
            example: conv_V1StGXR8Z5jdHi6B
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - message
                - role
              additionalProperties: false
              properties:
                message:
                  type: string
                  description: Message text content to add to the conversation
                  example: Hello, how can I help you?
                role:
                  type: string
                  enum:
                    - user
                    - assistant
                  description: Role of the message sender
                  example: user
                actor_id:
                  x-naturali-ref: actors
                  type: string
                  nullable: true
                  description: Optional actor ID to associate with this message (user identity)
                  example: actor_V1StGXR8Z5jdHi6B
                position:
                  type: integer
                  description: Zero-based position. Defaults to MAX+1 (append).
                  example: 0
                metadata:
                  description: "Caller-owned annotations on the message (e.g. a phone number, a channel), stored as
                    sent and returned verbatim. The platform does not read the bag, so nothing in it
                    reaches the model: a value the model should see belongs in `message`."
                  example:
                    phone: "5511999998888"
                    channel: whatsapp
                  allOf:
                    - $ref: "#/components/schemas/NullableMetadataBag"
      responses:
        "201":
          description: Message added
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ConversationMessageRecord"
        "400":
          description: Invalid request body
          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"
        "404":
          description: Conversation or actor not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    parameters:
      - $ref: "#/components/parameters/ProjectId"
  /v1/projects/{project_id}/conversations/{conversation_id}/generate:
    post:
      tags:
        - Conversations
      summary: Generate the next message in a conversation
      description: |
        Generates the next message using the specified actor's linked agent or chat.
        Background by default: returns `202 Accepted` immediately and the reply
        lands as a new ConversationMessage when it completes — poll
        `GET /v1/projects/{project_id}/conversations/{conversation_id}/messages` for it.
        Pass `?wait=true` to block and receive the result inline. On
        `completed`, the reply is persisted as a new ConversationMessage
        authored by that actor. On `requires_action`, nothing is persisted; the
        caller must submit tool outputs via the Agents module and re-invoke
        generate — so a flow using client tools should pass `?wait=true`.
      operationId: generateConversationMessage
      x-naturali-resource:
        kind: conversation
        from: conversation_id
      parameters:
        - name: conversation_id
          in: path
          required: true
          schema:
            type: string
        - name: wait
          in: query
          required: false
          x-naturali-tool-forced: true
          description: When omitted or `false` (default), the generation runs in the background and `202
            Accepted` is returned immediately. Pass `true` to block until the generation settles and
            receive the result. An MCP tool call always waits.
          schema:
            type: boolean
            default: false
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - agent_id
              additionalProperties: false
              properties:
                agent_id:
                  x-naturali-ref: agents
                  type: string
                  description: ID of the agent that will produce the next message.
                model:
                  type: string
                  description: Optional model override.
                stream:
                  type: boolean
                  description: If true, stream tokens via SSE. NOT IMPLEMENTED in v1 — returns 501.
                tool_context:
                  type: object
                  additionalProperties:
                    type: string
                  nullable: true
                  description: Key-value pairs forwarded as `X-Naturali-Context-<key>` headers on every `http` and
                    `mcp` tool call in this generation. The header name is the deployment's
                    configured context prefix (`X-Naturali-Context-` by default) plus the key
                    verbatim — no character is re-cased and keys are never case-converted, so they
                    round-trip exactly as sent. An invalid or colliding key is rejected with `400
                    INVALID_TOOL_CONTEXT_KEY`.
      responses:
        "200":
          description: Generation completed or requires action (only when `?wait=true`)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GenerateConversationMessageResponse"
        "202":
          description: Generation accepted and running in the background (default, when `wait` is omitted or
            `false`). The reply is persisted as a ConversationMessage when it completes.
          content:
            application/json:
              schema:
                type: object
                required:
                  - status
                  - conversation_id
                properties:
                  status:
                    type: string
                    enum:
                      - accepted
                    example: accepted
                  conversation_id:
                    type: string
                    example: conv_V1StGXR8Z5jdHi6B
        "400":
          description: Invalid request
          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"
        "404":
          description: Conversation or actor not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "429":
          description: "`QUOTA_EXCEEDED`: an `enforce`-mode generation quota is exhausted. `error.meta`
            carries `quota_id`, `metric`, `limit`, `window` and `resets_at`, with a `Retry-After`
            header. Only with `?wait=true`: a background call is answered `202` before the quota is
            read."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "501":
          description: Streaming not implemented
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    parameters:
      - $ref: "#/components/parameters/ProjectId"
  /v1/projects/{project_id}/conversations/{conversation_id}/messages/{document_id}:
    delete:
      tags:
        - Conversations
      summary: Remove a message from a conversation
      description: Removes a document from a conversation
      operationId: removeConversationMessage
      x-naturali-resource:
        kind: conversation
        from: conversation_id
      parameters:
        - name: conversation_id
          in: path
          required: true
          description: Conversation ID
          schema:
            type: string
            example: conv_V1StGXR8Z5jdHi6B
        - name: document_id
          in: path
          required: true
          description: Document ID
          schema:
            type: string
            example: doc_V1StGXR8Z5jdHi6B
      responses:
        "204":
          description: Message removed
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Conversation or message not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    parameters:
      - $ref: "#/components/parameters/ProjectId"
  /v1/projects/{project_id}/conversations/{conversation_id}/tags:
    get:
      tags:
        - Conversations
      summary: Get conversation tags
      description: Returns all tags attached to the conversation
      operationId: getConversationTags
      x-naturali-resource:
        kind: conversation
        from: conversation_id
      parameters:
        - name: conversation_id
          in: path
          required: true
          description: Conversation ID
          schema:
            type: string
            example: conv_V1StGXR8Z5jdHi6B
      responses:
        "200":
          description: Conversation tags
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TagBag"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Conversation not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    put:
      tags:
        - Conversations
      summary: Replace conversation tags
      description: Replaces all tags on the conversation with the provided tags
      operationId: replaceConversationTags
      x-naturali-resource:
        kind: conversation
        from: conversation_id
      parameters:
        - name: conversation_id
          in: path
          required: true
          description: Conversation ID
          schema:
            type: string
            example: conv_V1StGXR8Z5jdHi6B
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TagBag"
      responses:
        "200":
          description: Tags replaced
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TagBag"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Conversation not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    patch:
      tags:
        - Conversations
      summary: Merge conversation tags
      description: Merges provided tags into the conversation's existing tags (existing tags are preserved
        unless overridden)
      operationId: mergeConversationTags
      x-naturali-resource:
        kind: conversation
        from: conversation_id
      parameters:
        - name: conversation_id
          in: path
          required: true
          description: Conversation ID
          schema:
            type: string
            example: conv_V1StGXR8Z5jdHi6B
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TagBag"
      responses:
        "200":
          description: Tags merged
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TagBag"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Conversation not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    parameters:
      - $ref: "#/components/parameters/ProjectId"
components:
  schemas:
    ConversationRecord:
      type: object
      properties:
        id:
          type: string
          description: Conversation ID
          example: conv_V1StGXR8Z5jdHi6B
        project_id:
          x-naturali-ref: projects
          type: string
          description: Project ID
          example: proj_V1StGXR8Z5jdHi6B
        name:
          type: string
          nullable: true
          description: Optional human-readable name for the conversation.
        status:
          type: string
          enum:
            - open
            - closed
          description: Conversation status
          example: open
        created_at:
          type: string
          format: date-time
          description: Creation timestamp
        updated_at:
          type: string
          format: date-time
          description: Last update timestamp
        actor_id:
          x-naturali-ref: actors
          type: string
          nullable: true
          description: Actor ID associated with this conversation
          example: actor_V1StGXR8Z5jdHi6B
        retrieval:
          type: string
          enum:
            - embed
            - none
            - null
          nullable: true
          description: Whether this conversation's turns are embedded for vector retrieval. Turns are stored
            and chunked either way, so `none` leaves them readable and reachable by full-text search
            without paying for an embedding. `null` (the default) inherits the project's
            `default_conversation_retrieval`.
    ConversationMessageRecord:
      type: object
      properties:
        document_id:
          x-naturali-ref: documents
          type: string
          description: Document ID
          example: doc_V1StGXR8Z5jdHi6B
        role:
          type: string
          enum:
            - user
            - assistant
            - system
          description: Role of the message sender
          example: user
        actor_id:
          x-naturali-ref: actors
          type: string
          nullable: true
          description: Optional actor ID associated with this message
          example: actor_V1StGXR8Z5jdHi6B
        agent_id:
          x-naturali-ref: agents
          type: string
          nullable: true
          description: Optional agent ID that generated this message (set for assistant messages produced by
            generate)
          example: agent_V1StGXR8Z5jdHi6B
        position:
          type: integer
          description: Zero-based position in the conversation
          example: 0
        metadata:
          description: Optional structured metadata attached to the message
          example:
            phone: "5511999998888"
            channel: whatsapp
          allOf:
            - $ref: "#/components/schemas/NullableMetadataBag"
        content:
          type: string
          nullable: true
          description: Full text content of the message
    GenerateConversationMessageCompleted:
      type: object
      required:
        - status
        - content
        - message
        - generation_id
        - trace_id
      properties:
        status:
          type: string
          enum:
            - completed
          description: Indicates generation finished successfully.
        content:
          type: string
          description: >
            The AI-generated text of the reply. This is the canonical field for the assistant's
            response text.
          example: Hello! How can I help you today?
        message:
          $ref: "#/components/schemas/ConversationMessageRecord"
        generation_id:
          x-naturali-ref: generations
          type: string
          description: ID of the underlying generation record.
          example: gen_V1StGXR8Z5jdHi6B
        trace_id:
          x-naturali-ref: traces
          type: string
          description: Trace ID for observability.
          example: trace_V1StGXR8Z5jdHi6B
        model:
          type: string
          description: Model used for generation.
          example: gpt-4o
    GenerateConversationMessageRequiresAction:
      type: object
      required:
        - status
        - generation_id
        - trace_id
        - required_action
      properties:
        status:
          type: string
          enum:
            - requires_action
          description: >
            Indicates the agent requires tool-call outputs before it can produce a reply. No message
            is persisted yet.
        generation_id:
          x-naturali-ref: generations
          type: string
          description: ID of the paused generation. Pass to the tool-outputs endpoint.
          example: gen_V1StGXR8Z5jdHi6B
        trace_id:
          x-naturali-ref: traces
          type: string
          description: Trace ID for observability.
          example: trace_V1StGXR8Z5jdHi6B
        required_action:
          type: object
          description: Tool-call information the client must resolve.
    GenerateConversationMessageResponse:
      oneOf:
        - $ref: "#/components/schemas/GenerateConversationMessageCompleted"
        - $ref: "#/components/schemas/GenerateConversationMessageRequiresAction"
      discriminator:
        propertyName: status
        mapping:
          completed: "#/components/schemas/GenerateConversationMessageCompleted"
          requires_action: "#/components/schemas/GenerateConversationMessageRequiresAction"
    ErrorResponse:
      type: object
      required:
        - error
      properties:
        error:
          type: object
          description: Structured error. Every error response uses this shape, so `code` can be read without
            first checking the type of `error`.
          required:
            - code
            - message
          properties:
            code:
              type: string
              description: "Machine-readable error code: lower_snake for an error naturali raises
                (`access_denied`), UPPER_SNAKE for one the runtime reports (`RESOURCE_NOT_FOUND`)."
              example: access_denied
            message:
              type: string
              description: Human-readable explanation.
              example: Your role in this project does not carry this action.
            details:
              type: object
              additionalProperties: true
              description: Structured context for an error naturali raises, such as the `resource` and `limit` of
                a `plan_limit_reached`.
            meta:
              type: object
              description: Structured context for an error the runtime reports.
    NullableMetadataBag:
      type: object
      nullable: true
      description: A `MetadataBag` on a field where `null` is meaningful — a full-replacement update that
        clears the bag, or a record whose bag was never set.
      example:
        author: John
        revision: 2
    TagBag:
      type: object
      additionalProperties:
        type: string
      x-cli-flag-name: tags
      description: >-
        Key-value labels on a resource. A flat object of string values — an array, a nested object
        or a number is rejected with `400 VALIDATION_FAILED`, never coerced. Keys are opaque and
        stored verbatim, so `cost_center` and `costCenter` are two different tags. Matched by JSONB
        containment wherever tags are read: the `?tags=` filter and knowledge search.


        Keys beginning `system.` are reserved: the platform writes them to record which
        conversation, actor, agent and role a row came from, and a write naming one is refused with
        `400 RESERVED_TAG_KEY`. They are read and filtered like any other tag.


        The bag is bounded, because every pair reaches the IAM context of every access check on the
        resource: at most 50 keys, each key at most 128 characters and each value at most 256. A
        write past a bound — including a merge that would grow the stored bag past the key count —
        is `400 VALIDATION_FAILED` with `meta.limit` naming the bound it crossed. `system.*` keys
        are the platform's and do not count against the 50.
      example:
        team: finance
        env: prod
  parameters:
    ProjectId:
      name: project_id
      in: path
      required: true
      description: Project public ID (proj_ prefix).
      schema:
        type: string
        example: proj_V1StGXR8Z5jdHi6B
    TagsQuery:
      name: tags
      in: query
      required: false
      description: >
        Filter by tag pairs, written `key:value` (split on the first colon, so a value may contain
        colons). Repeat the parameter for several pairs; **all** must be present with exactly that
        value.
      schema:
        type: array
        items:
          type: string
      style: form
      explode: true
      example:
        - env:prod
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: A naturali API key (nat_sk_…) or a session JWT.
    oauth2:
      type: oauth2
      description: "A connected app's OAuth access token, issued by this API's authorization server
        (discovery: /.well-known/oauth-authorization-server). Its one scope carries every operation,
        confined to the projects the user chose when approving the app."
      flows:
        authorizationCode:
          authorizationUrl: https://api.naturali.ai/authorize
          tokenUrl: https://api.naturali.ai/token
          refreshUrl: https://api.naturali.ai/token
          scopes:
            mcp:access: Every operation this API serves, on the projects the grant covers.
