openapi: 3.0.3
info:
  title: SOAT Conversations API
  version: 1.0.0
  description: API for managing conversations and their messages
  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: Conversations
    description: Manage conversations
security:
  - bearerAuth: []
paths:
  /api/v1/conversations:
    get:
      tags:
        - Conversations
      summary: List conversations
      description: Returns all conversations the caller has access to. If projectId is provided, returns only conversations in that project. project keys are scoped to a single project automatically.
      operationId: listConversations
      parameters:
        - name: project_id
          in: query
          required: false
          description: Project ID (optional)
          schema:
            type: string
            example: 'proj_V1StGXR8Z5jdHi6B'
        - name: actor_id
          in: query
          required: false
          description: Filter by actor ID
          schema:
            type: string
            example: 'act_V1StGXR8Z5jdHi6B'
        - name: limit
          in: query
          required: false
          description: Maximum number of results to return
          schema:
            type: integer
            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. project keys automatically infer the project from the key's scope; JWT callers must supply projectId.
      operationId: createConversation
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                project_id:
                  type: string
                  description: Project ID. Required for JWT auth; omit when using an project key.
                  example: 'proj_V1StGXR8Z5jdHi6B'
                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:
                  type: string
                  nullable: true
                  description: Actor ID to associate with this conversation
                  example: 'act_V1StGXR8Z5jdHi6B'
      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'
  /api/v1/conversations/{conversation_id}:
    get:
      tags:
        - Conversations
      summary: Get a conversation by ID
      description: Returns a conversation by its ID
      operationId: getConversation
      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
      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:
                - status
              properties:
                status:
                  type: string
                  enum: [open, closed]
                  description: New conversation status
      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
      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'
  /api/v1/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
      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
            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
      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
              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, system]
                  description: Role of the message sender
                  example: 'user'
                actor_id:
                  type: string
                  nullable: true
                  description: Optional actor ID to associate with this message (user identity)
                  example: 'act_V1StGXR8Z5jdHi6B'
                position:
                  type: integer
                  description: Zero-based position. Defaults to MAX+1 (append).
                  example: 0
                metadata:
                  type: object
                  description: Optional structured metadata to attach to the message (e.g. phone number, channel). Stored as-is and injected into the AI prompt context.
                  nullable: true
                  additionalProperties: true
                  example:
                    phone: '5511999998888'
                    channel: 'whatsapp'
      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'
  /api/v1/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.
        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.
      operationId: generateConversationMessage
      parameters:
        - name: conversation_id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - agent_id
              properties:
                agent_id:
                  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 injected as context headers into all tool call requests made during this generation.
      responses:
        '200':
          description: Generation completed or requires action
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GenerateConversationMessageResponse'
        '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'
        '501':
          description: Streaming not implemented
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /api/v1/conversations/{conversation_id}/actors:
    get:
      tags:
        - Conversations
      summary: List actors in a conversation
      description: Returns all distinct actors who have sent at least one message in the conversation
      operationId: listConversationActors
      parameters:
        - name: conversation_id
          in: path
          required: true
          description: Conversation ID
          schema:
            type: string
            example: 'conv_V1StGXR8Z5jdHi6B'
      responses:
        '200':
          description: List of actors
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/ConversationActorRecord'
        '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'
  /api/v1/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
      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'
  /api/v1/conversations/{conversation_id}/tags:
    get:
      tags:
        - Conversations
      summary: Get conversation tags
      description: Returns all tags attached to the conversation
      operationId: getConversationTags
      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:
                type: object
                additionalProperties:
                  type: string
        '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
      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:
                type: string
      responses:
        '200':
          description: Tags replaced
          content:
            application/json:
              schema:
                type: object
                additionalProperties:
                  type: string
        '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 with existing tags
      operationId: mergeConversationTags
      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:
                type: string
      responses:
        '200':
          description: Tags merged
          content:
            application/json:
              schema:
                type: object
                additionalProperties:
                  type: string
        '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'
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: JWT token or sk_ api key
  schemas:
    ConversationRecord:
      type: object
      properties:
        id:
          type: string
          description: Conversation ID
          example: 'conv_V1StGXR8Z5jdHi6B'
        project_id:
          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:
          type: string
          nullable: true
          description: Actor ID associated with this conversation
          example: 'act_V1StGXR8Z5jdHi6B'
    ConversationMessageRecord:
      type: object
      properties:
        document_id:
          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:
          type: string
          nullable: true
          description: Optional actor ID associated with this message
          example: 'act_V1StGXR8Z5jdHi6B'
        agent_id:
          type: string
          nullable: true
          description: Optional agent ID that generated this message (set for assistant messages produced by generate)
          example: 'agt_V1StGXR8Z5jdHi6B'
        position:
          type: integer
          description: Zero-based position in the conversation
          example: 0
        metadata:
          type: object
          description: Optional structured metadata attached to the message
          nullable: true
          additionalProperties: true
          example:
            phone: '5511999998888'
            channel: 'whatsapp'
        content:
          type: string
          nullable: true
          description: Full text content of the message
    ConversationActorRecord:
      type: object
      properties:
        id:
          type: string
          description: Actor ID
          example: 'act_V1StGXR8Z5jdHi6B'
        project_id:
          type: string
          description: Project ID
          example: 'proj_V1StGXR8Z5jdHi6B'
        name:
          type: string
          description: Actor name
          example: 'Alice'
        type:
          type: string
          description: Actor type
          example: 'human'
        external_id:
          type: string
          description: External identifier
          example: 'ext_123'
        created_at:
          type: string
          format: date-time
          description: Creation timestamp
        updated_at:
          type: string
          format: date-time
          description: Last update timestamp
    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:
          type: string
          description: ID of the underlying generation record.
          example: 'gen_V1StGXR8Z5jdHi6B'
        trace_id:
          type: string
          description: Trace ID for observability.
          example: 'trc_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:
          type: string
          description: ID of the paused generation. Pass to the tool-outputs endpoint.
          example: 'gen_V1StGXR8Z5jdHi6B'
        trace_id:
          type: string
          description: Trace ID for observability.
          example: 'trc_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
      properties:
        error:
          type: string
          description: Error message
          example: 'Conversation not found'
