openapi: "3.1.0"
info:
  title: MiniApp — Eventos de Entrada
  description: >
    Eventos de entrada do aplicativo externo (MiniApp → Acloud).
    O aplicativo externo envia uma requisicao POST para o endpoint do hook.
  version: "2.0.0"

webhooks:

  message.new.personal:
    post:
      summary: Nova mensagem de entrada do usuario
      description: >
        Enviado pelo aplicativo externo ao receber uma nova mensagem do usuario.
        Tambem usado como confirmacao de inicializacao do chat em resposta ao hook
        de saida chat.init.personal.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - method
                - payload
              properties:
                method:
                  type: string
                  enum:
                    - message.new.personal
                payload:
                  $ref: '#/components/schemas/MessageNewPayload'
            examples:
              message_new:
                summary: Nova mensagem
                value:
                  method: message.new.personal
                  payload:
                    external_message_id: msg_123
                    external_user_id: user_456
                    external_chat_id: chat_789
                    text: Ola, quero perguntar sobre meu pedido
                    send_date: 1710752400
                    user_data:
                      name: Joao Silva
                      username: joao
                      phone: "+79001234567"
                      email: joao@example.com
                      avatar_url: https://example.com/avatar.jpg
                      public_link: https://example.com/joao
              message_new_with_attachment:
                summary: Nova mensagem com anexo
                value:
                  method: message.new.personal
                  payload:
                    external_message_id: msg_124
                    external_user_id: user_456
                    external_chat_id: chat_789
                    text: Aqui esta a captura de tela que pediram
                    send_date: 1710752500
                    user_data:
                      name: Joao Silva
                      username: joao
                      phone: "+79001234567"
                      email: joao@example.com
                      avatar_url: https://example.com/avatar.jpg
                      public_link: https://example.com/joao
                    attachments:
                      - id: ext_att_1001
                        url: https://miniapp.example/files/1001
                        filename: screenshot.png
                        size: 204800
                        type: photo
              message_new_with_failed_attachments:
                summary: >
                  Nova mensagem com declaracao de entrega parcial de anexos:
                  o aplicativo externo entregou alguns arquivos e notifica o Acloud
                  que alguns nao foram incluidos no array attachments.
                value:
                  method: message.new.personal
                  payload:
                    external_message_id: msg_125
                    external_user_id: user_456
                    external_chat_id: chat_789
                    text: Enviando foto
                    send_date: 1710752600
                    user_data:
                      name: Joao Silva
                      username: joao
                      phone: "+79001234567"
                    attachments:
                      - id: ext_att_1002
                        url: https://miniapp.example/files/1002
                        filename: photo_1.jpg
                        size: 524288
                        type: photo
                    has_failed_attachments: true
                    failed_attachments_count: 4
      responses:
        "200":
          description: Evento processado com sucesso
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
        "400":
          description: Erro de validacao (formato invalido ou campos obrigatorios ausentes)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "409":
          description: >
            Bot desativado ou conta bloqueada.
            Pare de enviar eventos ate receber o hook de saida bot.activated.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConflictResponse'
        "410":
          description: >
            Bot ou conta excluidos.
            Remova a integracao do seu lado e pare de enviar eventos.
        "500":
          description: Erro interno do servidor
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                success: false
                message: Internal error

  message.delete.personal:
    post:
      summary: Mensagem excluida pelo usuario
      description: >
        Enviado pelo aplicativo externo quando o usuario exclui uma mensagem.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - method
                - payload
              properties:
                method:
                  type: string
                  enum:
                    - message.delete.personal
                payload:
                  $ref: '#/components/schemas/MessageDeletedPayload'
            examples:
              message_deleted:
                summary: Exclusao de mensagem
                value:
                  method: message.delete.personal
                  payload:
                    external_message_id: msg_123
                    deleted_date: 1710752400
      responses:
        "200":
          description: Evento processado com sucesso
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
        "400":
          description: Erro de validacao (formato invalido ou campos obrigatorios ausentes)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "404":
          description: >
            Mensagem com o external_message_id especificado nao encontrada no escopo do bot
            proprietario do webhook (cross-bot, nao existe, ou a mensagem e uma mensagem de
            saida do gestor e nao pode ser modificada externamente).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                success: false
                message: Not found
        "409":
          description: >
            Bot desativado ou conta bloqueada.
            Pare de enviar eventos ate receber o hook de saida bot.activated.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConflictResponse'
        "410":
          description: >
            Bot ou conta excluidos.
            Remova a integracao do seu lado e pare de enviar eventos.
        "500":
          description: Erro interno do servidor
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                success: false
                message: Internal error

  message.edit.personal:
    post:
      summary: Mensagem editada pelo usuario
      description: >
        Enviado pelo aplicativo externo quando o usuario edita uma mensagem.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - method
                - payload
              properties:
                method:
                  type: string
                  enum:
                    - message.edit.personal
                payload:
                  $ref: '#/components/schemas/MessageEditedPayload'
            examples:
              message_edited:
                summary: Edicao de mensagem
                value:
                  method: message.edit.personal
                  payload:
                    external_message_id: msg_123
                    edited_date: 1710752400
                    new_text: Texto da mensagem corrigido
      responses:
        "200":
          description: Evento processado com sucesso
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
        "400":
          description: Erro de validacao (formato invalido ou campos obrigatorios ausentes)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "404":
          description: >
            Mensagem com o external_message_id especificado nao encontrada no escopo do bot
            proprietario do webhook (cross-bot, nao existe, ou a mensagem e uma mensagem de
            saida do gestor e nao pode ser modificada externamente).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                success: false
                message: Not found
        "409":
          description: >
            Bot desativado ou conta bloqueada.
            Pare de enviar eventos ate receber o hook de saida bot.activated.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConflictResponse'
        "410":
          description: >
            Bot ou conta excluidos.
            Remova a integracao do seu lado e pare de enviar eventos.
        "500":
          description: Erro interno do servidor
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                success: false
                message: Internal error

  error:
    post:
      summary: Notificacao de erro no processamento de hook de saida
      description: >
        O aplicativo externo envia este hook se nao conseguiu processar uma requisicao de
        saida do Acloud (por exemplo, usuario nao encontrado no sistema). O campo event_id
        e retornado sem alteracoes a partir do campo event_id do payload do hook de saida.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - method
                - payload
              properties:
                method:
                  type: string
                  enum:
                    - error
                payload:
                  $ref: '#/components/schemas/ErrorPayload'
            examples:
              error_user_not_found:
                summary: Usuario nao encontrado (erro message.new.personal)
                value:
                  method: error
                  payload:
                    message: "User not found"
                    event_id: '550e8400-e29b-41d4-a716-446655440000:79000'
              error_chat_init:
                summary: Erro de inicializacao do chat (chat.init.personal)
                value:
                  method: error
                  payload:
                    message: "User lookup failed"
                    event_id: '7c9e6679-7425-40de-944b-e07fc1f90ae7:79000'
      responses:
        "200":
          description: Erro aceito e processado
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
        "400":
          description: Erro de validacao (formato invalido ou campos obrigatorios ausentes)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "409":
          description: >
            Bot desativado ou conta bloqueada.
            Pare de enviar eventos ate receber o hook de saida bot.activated.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConflictResponse'
        "410":
          description: >
            Bot ou conta excluidos.
            Remova a integracao do seu lado e pare de enviar eventos.
        "500":
          description: Erro interno do servidor
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                success: false
                message: Internal error

  message.completed.personal:
    post:
      summary: Confirmacao de entrega de mensagem de saida
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - method
                - payload
              properties:
                method:
                  type: string
                  enum:
                    - message.completed.personal
                payload:
                  $ref: '#/components/schemas/MessageCompletedPayload'
            examples:
              message_completed:
                summary: Confirmacao de entrega completa de mensagem de saida
                value:
                  method: message.completed.personal
                  payload:
                    inner_message_id: 9001
                    external_message_id: ext_msg_555
              message_completed_partial:
                summary: >
                  Confirmacao de entrega parcial de mensagem com arquivos:
                  alguns anexos entregues ao cliente, outros nao
                value:
                  method: message.completed.personal
                  payload:
                    inner_message_id: 9002
                    external_message_id: ext_msg_556
                    delivered_attachments:
                      - "555"
                    failed_attachments:
                      - "556"
      responses:
        "200":
          description: Evento processado com sucesso
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
        "400":
          description: Erro de validacao (formato invalido ou campos obrigatorios ausentes)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "404":
          description: >
            Mensagem de saida com o inner_message_id especificado nao encontrada no escopo do
            bot proprietario do webhook (cross-bot — tentativa de confirmar entrega de mensagem
            de outro bot).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                success: false
                message: Not found
        "409":
          description: >
            Bot desativado ou conta bloqueada.
            Pare de enviar eventos ate receber o hook de saida bot.activated.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConflictResponse'
        "410":
          description: >
            Bot ou conta excluidos.
            Remova a integracao do seu lado e pare de enviar eventos.
        "500":
          description: Erro interno do servidor
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                success: false
                message: Internal error

  message.read.personal:
    post:
      summary: Mensagem lida pelo usuario (aceita como no-op)
      description: >
        Enviado pelo aplicativo externo quando o usuario le uma mensagem.
        O Acloud aceita o evento e retorna 200 OK, mas **o tratamento completo de leitura
        ainda nao esta implementado** (no-op handler) — a semantica real de leitura e trabalho
        futuro. O contrato esta fixado para que o aplicativo externo que envia este hook nao
        receba 5xx.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - method
                - payload
              properties:
                method:
                  type: string
                  enum:
                    - message.read.personal
                payload:
                  $ref: '#/components/schemas/MessageReadPayload'
            examples:
              message_read:
                summary: Mensagem lida
                value:
                  method: message.read.personal
                  payload:
                    message_id: msg_123
                    read_date: 1710752400
                    user_id: user_456
                    chat_id: chat_789
      responses:
        "200":
          description: Evento aceito (no-op, sem efeitos colaterais)
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
        "409":
          description: >
            Bot desativado ou conta bloqueada.
            Pare de enviar eventos ate receber o hook de saida bot.activated.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConflictResponse'
        "410":
          description: >
            Bot ou conta excluidos.
            Remova a integracao do seu lado e pare de enviar eventos.
        "500":
          description: Erro interno do servidor
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                success: false
                message: Internal error


components:
  schemas:

    IncomingEvent:
      type: object
      required:
        - method
        - payload
      properties:
        method:
          type: string
          enum:
            - message.new.personal
            - message.completed.personal
            - message.delete.personal
            - message.edit.personal
            - message.read.personal
            - error
          description: Tipo de evento
        payload:
          oneOf:
            - $ref: '#/components/schemas/MessageNewPayload'
            - $ref: '#/components/schemas/MessageCompletedPayload'
            - $ref: '#/components/schemas/MessageDeletedPayload'
            - $ref: '#/components/schemas/MessageEditedPayload'
            - $ref: '#/components/schemas/MessageReadPayload'
            - $ref: '#/components/schemas/ErrorPayload'
          discriminator:
            propertyName: method

    # ─── message.new.personal ─────────────────────────────────────────────────

    MessageNewPayload:
      type: object
      description: Nova mensagem de entrada de um usuario do aplicativo externo
      required:
        - external_message_id
        - external_user_id
        - external_chat_id
      properties:
        external_message_id:
          type: string
          description: ID unico da mensagem no sistema externo
        external_user_id:
          type: string
          description: ID unico do usuario no sistema externo
        external_chat_id:
          type: string
          description: ID unico do chat no sistema externo
        text:
          type: string
          default: ""
          description: Texto da mensagem
        send_date:
          type: integer
          description: Unix timestamp do momento de envio da mensagem
          example: 1710752400
        user_data:
          $ref: '#/components/schemas/UserData'
        attachments:
          type: array
          default: []
          items:
            $ref: '#/components/schemas/AttachmentObject'
          description: >
            Array de anexos da mensagem. Se nao especificado ou vazio,
            a mensagem consiste apenas em texto.
        direction:
          type: ["integer", "null"]
          enum: [0, 1, null]
          default: null
          description: >
            Direcao da mensagem. 0 — entrada (do usuario para o gestor),
            1 — saida (do gestor para o usuario, enviada no sistema externo).
            Se nao especificado — tratada como entrada.
        has_failed_attachments:
          type: ["boolean", "null"]
          default: null
          description: >
            Indicador opcional declarado pelo aplicativo externo de que a mensagem original
            do usuario continha mais anexos do que foram efetivamente passados ao Acloud no
            array attachments. A razao pode ser qualquer coisa do lado do aplicativo externo
            (limite de tamanho excedido, erro de download, politica do mensageiro, etc.).
            Atualmente o Acloud aceita o campo e registra o fato, mas nao realiza processamento
            especial nem o salva no BD. O campo e reservado para implementacao futura — simetrico
            com delivered_attachments / failed_attachments no hook de saida message.completed.personal.
        failed_attachments_count:
          type: ["integer", "null"]
          minimum: 0
          default: null
          description: >
            Numero opcional de arquivos que o aplicativo externo nao entregou.
            So faz sentido quando has_failed_attachments=true. Se enviado sem
            has_failed_attachments=true, o valor e ignorado e um aviso e escrito no log do Acloud.

    # ─── message.delete.personal ──────────────────────────────────────────────

    MessageDeletedPayload:
      type: object
      description: Evento de exclusao de mensagem pelo usuario no sistema externo.
      required:
        - external_message_id
      properties:
        external_message_id:
          type: string
          description: ID da mensagem no sistema externo
          example: msg_123
        deleted_date:
          type: integer
          description: Unix timestamp do momento de exclusao da mensagem
          example: 1710752400

    # ─── message.read.personal ────────────────────────────────────────────────

    MessageReadPayload:
      type: object
      description: >
        Evento de leitura de mensagem pelo usuario no sistema externo.
        O Acloud aceita o payload, mas na versao atual **nao o processa**
        (no-op handler). O contrato esta fixado para compatibilidade com o processamento
        futuro; todos os campos sao opcionais (defaults no DTO).
      properties:
        message_id:
          type: string
          description: ID da mensagem lida no sistema externo
          example: msg_123
          default: ""
        read_date:
          type: integer
          description: Unix timestamp do momento de leitura
          example: 1710752400
        user_id:
          type: string
          description: ID do usuario que leu a mensagem
          example: user_456
          default: ""
        chat_id:
          type: string
          description: ID do chat
          example: chat_789
          default: ""

    # ─── message.edit.personal ────────────────────────────────────────────────

    MessageEditedPayload:
      type: object
      description: Evento de edicao de mensagem pelo usuario no sistema externo.
      required:
        - external_message_id
      properties:
        external_message_id:
          type: string
          description: ID da mensagem no sistema externo
          example: msg_123
        edited_date:
          type: integer
          description: Unix timestamp do momento de edicao da mensagem
          example: 1710752400
        new_text:
          type: string
          description: Novo texto da mensagem apos a edicao
          default: ""
          example: Texto da mensagem corrigido

    # ─── message.completed.personal ───────────────────────────────────────────

    MessageCompletedPayload:
      type: object
      description: >
        O aplicativo externo confirma a entrega bem-sucedida de uma mensagem enviada pelo
        nosso sistema. Usado para vincular inner_message_id (nosso) com external_message_id (deles).


        Comportamento com entrega parcial de arquivos: se alguns arquivos do array attachments
        do hook de saida foram entregues ao destinatario e outros nao, o aplicativo externo
        igualmente envia message.completed.personal e especifica o resultado de entrega de cada
        arquivo nos campos delivered_attachments e failed_attachments.

        Se todos os arquivos foram entregues — os campos podem ser omitidos ou a lista completa
        enviada em delivered_attachments.

        Se nenhum arquivo pudo ser entregue (e a parte de texto tambem e invalida para o cliente)
        — um evento error e enviado em vez de message.completed.personal.
      required:
        - inner_message_id
        - external_message_id
      properties:
        inner_message_id:
          type: integer
          description: ID da mensagem no nosso sistema (o valor inner_message_id do hook de saida message.new.personal)
          example: 9001
        external_message_id:
          type: string
          description: ID atribuido a mensagem no sistema externo
          example: ext_msg_555
        delivered_attachments:
          type: array
          default: []
          items:
            type: string
          description: >
            Lista de IDs de anexos (correspondentes ao id do array attachments do
            message.new.personal de saida) que foram entregues com sucesso ao destinatario.
            Campo opcional: pode ser omitido se todos os anexos foram entregues.

            IMPORTANTE: na versao atual o Acloud aceita este campo mas nao o usa para logica
            especial — o status da mensagem e definido como OK (entrega completa). O campo e
            reservado para o tratamento futuro de entrega parcial. O aplicativo externo deve
            envia-lo agora para nao precisar alterar seu lado quando o tratamento for adicionado.
          example: ["555", "556"]
        failed_attachments:
          type: array
          default: []
          items:
            type: string
          description: >
            Lista de IDs de anexos que nao puderam ser entregues ao destinatario.
            Campo opcional: pode ser omitido se todos os anexos foram entregues.

            IMPORTANTE: na versao atual o Acloud aceita este campo mas nao o usa para logica
            especial — o status da mensagem e definido como OK mesmo com failed_attachments.
            O campo e reservado para o tratamento futuro de entrega parcial. O aplicativo externo
            deve envia-lo agora para nao precisar alterar seu lado quando o tratamento for adicionado.
          example: ["557"]

    # ─── Shared types ─────────────────────────────────────────────────────────

    UserData:
      type: object
      description: Dados do usuario do sistema externo
      properties:
        name:
          type: ["string", "null"]
          description: Nome de exibicao
        username:
          type: ["string", "null"]
          description: Nome de usuario
        phone:
          type: ["string", "null"]
        email:
          type: ["string", "null"]
        avatar_url:
          type: ["string", "null"]
          description: URL do avatar
        public_link:
          type: ["string", "null"]
          description: Link publico do perfil

    AttachmentObject:
      type: object
      description: >
        Metadados do anexo na mensagem de entrada. O Acloud fara o download do arquivo
        pela URL fornecida e o armazenara. Tamanho maximo permitido — 50 MB
        (52428800 bytes); arquivos maiores sao rejeitados.
      required:
        - id
        - url
        - filename
      properties:
        id:
          type: string
          description: >
            ID unico do anexo no sistema externo.
            Usado para deduplicacao em chamadas repetidas ao webhook.
        url:
          type: string
          description: URL a partir da qual o Acloud faz o download do arquivo.
        filename:
          type: string
          description: Nome do arquivo com extensao.
        size:
          type: integer
          default: 0
          description: >
            Tamanho do arquivo em bytes. Nao deve exceder 50 MB (52428800).
            O Acloud verifica o tamanho antes e depois do download.
        type:
          type: string
          default: file
          description: >
            Indicacao do tipo de anexo (photo / file / video / audio).
            O Acloud nao usa este campo — o tipo real e determinado pelo mime do arquivo baixado.
        metadata:
          type: ["object", "null"]
          description: Metadados arbitrarios (opcional).

    # ─── error ────────────────────────────────────────────────────────────────

    ErrorPayload:
      type: object
      description: >
        Payload do evento error. O aplicativo externo sinaliza que nao conseguiu processar
        um hook de saida (message.new.personal ou chat.init.personal).
      required:
        - event_id
      properties:
        message:
          type: string
          description: Descricao do erro legivel por humanos
          example: "User not found"
        event_id:
          type: string
          description: >
            O valor do campo event_id do payload do hook de saida. Retornado sem alteracoes
            (como string opaca). Se o identificador nao for mais conhecido pelo Acloud (por
            exemplo, o TTL expirou), o evento e ignorado com seguranca sem efeitos colaterais.
          example: '550e8400-e29b-41d4-a716-446655440000:79000'

    # ─── Shared types ─────────────────────────────────────────────────────────

    ErrorResponse:
      type: object
      properties:
        success:
          type: boolean
          example: false
        message:
          type: string

    ConflictResponse:
      type: object
      description: >
        Bot desativado ou conta bloqueada. O aplicativo externo deve parar de enviar eventos
        para este bot ate receber o hook de saida bot.activated.
      required:
        - success
        - error
        - message
      properties:
        success:
          type: boolean
          example: false
        error:
          type: string
          description: Codigo de erro
          enum:
            - bot_deactivated
            - account_expired
          example: bot_deactivated
        message:
          type: string
          description: Descricao legivel da razao
          example: Bot is deactivated
