openapi: 3.1.0
info:
  title: Communications Service
  version: 0.2.0
  description: >
    Internal service responsible for operational communications flows such as
    reminders, notification dispatch, ingestion and push delivery.
servers:
  - url: https://communications-docs.ailian.com.br
    description: Current request host
tags:
  - name: health
  - name: docs
  - name: internal
paths:
  /health:
    get:
      tags: [health]
      summary: Health check
      responses:
        "200":
          description: Service is reachable
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: ok
                  service:
                    type: string
                    example: communications-service
  /ready:
    get:
      tags: [health]
      summary: Readiness check
      responses:
        "200":
          description: Readiness state
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: ready
                  dependencies:
                    type: object
  /metrics:
    get:
      tags: [health]
      summary: Prometheus metrics
      responses:
        "200":
          description: Prometheus metrics text format
          content:
            text/plain:
              schema:
                type: string
  /docs/email-templates.json:
    get:
      tags: [docs]
      summary: List local email templates
      description: >
        Returns the catalog of email templates versioned in this repository.
        Producers should use payload.email.template with one of these names
        instead of provider-specific template ids.
      responses:
        "200":
          description: Local email template catalog
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmailTemplateCatalogResponse'
              examples:
                catalog:
                  value:
                    templates:
                      - name: payment-approved
                        label: Pagamento aprovado
                        description: Confirma que o pagamento foi aprovado e orienta o usuário a continuar no app.
                        variables:
                          - name: appUrl
                            required: true
                            description: URL principal do app ou da tela relacionada ao pagamento.
                            example: https://app.ailian.com.br
                          - name: productName
                            required: false
                            description: Nome do plano, pacote de creditos ou item comprado.
                            example: Ghostwriter Starter
                          - name: paymentMethod
                            required: false
                            description: Forma de pagamento exibida ao cliente.
                            example: Cartao de credito
                        examplePayload:
                          email:
                            template: payment-approved
                            subject: Pagamento aprovado
                            variables:
                              appName: Ailian
                              customerName: Marina Costa
                              orderId: PED-2026-0001
                              productName: Ghostwriter Starter
                              paymentAmount: R$ 199,90
                              paymentMethod: Cartao de credito
                              paidAt: 11/05/2026 18:30
                              appUrl: https://app.ailian.com.br/billing
  /internal/notifications:
    post:
      tags: [internal]
      summary: Create a notification atomically
      description: >
        Canonical ingestion endpoint for n8n or other trusted producers. The
        service creates notifications, recipients and deliveries in a single
        operation, applies notification_preferences and queues eligible
        deliveries immediately when they are due.
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateNotificationRequest'
            examples:
              connectAiLeadHandoff:
                summary: ConectAi lead handoff with realtime and push
                value:
                  instanceId: 4f0kfmefs6x09lo
                  serviceId: z97bsc1rlv9qbk3
                  scope: user
                  source: workflow
                  eventKey: connectai.lead_assigned
                  title: Solicitação de contato recebida
                  description: Novo lead de Taboão da Serra - SP
                  body: |-
                    *Solicitação de contato recebida*

                    Olá Fulano, recebemos uma nova solicitação.

                    *Nome:* Beltrano
                    *Email:* beltrano@gmail.com
                    *Telefone:* 41988756111
                    *Estado:* SP
                    *Município:* Taboão da Serra
                    *Observação:* Interesse na 250 XRI

                    Registro incluído na planilha de acompanhamento.
                    https://docs.google.com/spreadsheets/d/1hsqqzdX8xrQF_gNHIRvFsEMVdVvqcfId2Jju9UMRh4s
                  bodyType: text
                  icon: i-lucide-bell-ring
                  navigationKey: connectai-lead-assigned-41988756111
                  priority: high
                  requireAction: true
                  entityType: contact
                  entityId: "41988756111"
                  dedupeKey: connectai:lead_assigned:41988756111:lead-20260327-01
                  groupKey: connectai:contact:41988756111
                  channels:
                    - realtime
                    - push
                  providers:
                    push: webpush
                    realtime: pocketbase
                  recipients:
                    - userId: y1412767403q7jx
                  actions:
                    - action: copy-handoff
                      title: Copiar mensagem
                    - action: open-whatsapp
                      title: Abrir WhatsApp
                    - action: open-sheet
                      title: Abrir planilha
                  payload:
                    kind: lead_handoff
                    ui:
                      copyOnOpen: body
                    lead:
                      name: Beltrano
                      email: beltrano@gmail.com
                      phone: "41988756111"
                      state: SP
                      city: Taboão da Serra
                      notes: Interesse na 250 XRI
                    links:
                      whatsAppShareUrl: https://wa.me/5511999999999?text=%2ASolicita%C3%A7%C3%A3o%20de%20contato%20recebida%2A%0A%0AOl%C3%A1%2C%20recebemos%20uma%20nova%20solicita%C3%A7%C3%A3o.
                      sheetUrl: https://docs.google.com/spreadsheets/d/1hsqqzdX8xrQF_gNHIRvFsEMVdVvqcfId2Jju9UMRh4s
                    push:
                      body: Novo lead de Taboão da Serra - SP
              contentServiceHtml:
                summary: HTML notification for content service
                value:
                  instanceId: 4f0kfmefs6x09lo
                  serviceId: x4q5cd5zj5qtwi3
                  scope: user
                  source: api
                  eventKey: content.review_ready
                  title: Conteúdo pronto para revisão
                  description: Um novo material foi gerado e está aguardando revisão.
                  body: "<p><strong>Revisão pronta</strong></p><p>Abra o painel para revisar e aprovar o conteúdo.</p>"
                  bodyType: html
                  icon: i-lucide-file-text
                  navigationKey: content-review-rvw-001
                  priority: normal
                  entityType: content_revision
                  entityId: rvw_001
                  channels:
                    - realtime
                  recipients:
                    - userId: y1412767403q7jx
                  payload:
                    kind: content_review
              paymentApprovedEmail:
                summary: Local email template for approved payment
                description: >
                  Usa template local versionado na repo. O provider Resend
                  recebe html/text/subject já renderizados pelo serviço.
                value:
                  instanceId: 4f0kfmefs6x09lo
                  serviceId: billing-service
                  scope: user
                  source: workflow
                  eventKey: billing.payment_approved
                  title: Pagamento aprovado
                  description: Pagamento confirmado com sucesso.
                  body: O pagamento foi confirmado e o acesso contratado já pode continuar.
                  bodyType: text
                  icon: i-lucide-credit-card
                  priority: normal
                  entityType: payment
                  entityId: pay_20260511_001
                  dedupeKey: billing:payment-approved:pay_20260511_001
                  groupKey: billing:payment:pay_20260511_001
                  channels:
                    - email
                  providers:
                    email: resend
                  recipients:
                    - userId: y1412767403q7jx
                  payload:
                    email:
                      template: payment-approved
                      subject: Pagamento aprovado
                      variables:
                        appName: Ailian
                        customerName: Marina Costa
                        orderId: PED-2026-0001
                        productName: Ghostwriter Starter
                        paymentAmount: R$ 199,90
                        paymentMethod: Cartao de credito
                        paidAt: 11/05/2026 18:30
                        appUrl: https://app.ailian.com.br/billing
              invoiceIssuedEmail:
                summary: Local email template for issued invoice
                description: >
                  Envia email quando a nota fiscal foi emitida, sem usar
                  templateId remoto do provider.
                value:
                  instanceId: 4f0kfmefs6x09lo
                  serviceId: billing-service
                  scope: user
                  source: workflow
                  eventKey: billing.invoice_issued
                  title: Nota fiscal emitida
                  description: Sua nota fiscal já está disponível.
                  body: A nota fiscal referente ao pagamento foi emitida.
                  bodyType: text
                  icon: i-lucide-file-check-2
                  priority: normal
                  entityType: invoice
                  entityId: nf_20260511_001
                  dedupeKey: billing:invoice-issued:nf_20260511_001
                  groupKey: billing:invoice:nf_20260511_001
                  channels:
                    - email
                  providers:
                    email: resend
                  recipients:
                    - userId: y1412767403q7jx
                  payload:
                    email:
                      template: invoice-issued
                      subject: Nota fiscal emitida
                      variables:
                        appName: Ailian
                        customerName: Marina Costa
                        invoiceNumber: NF-e 1024
                        productName: Creditos Ailian 20K
                        invoiceAmount: R$ 199,90
                        issuedAt: 11/05/2026 18:45
                        invoiceUrl: https://app.ailian.com.br/invoices/nf-001
                        appUrl: https://app.ailian.com.br/billing
              instanceLeadTemplates:
                summary: Instance audience with WhatsApp template and email payload
                description: >
                  Expande usuarios via user_instance_roles da instancia,
                  filtra por roles e usa os providers padrao chatwoot/resend.
                value:
                  instanceId: 4f0kfmefs6x09lo
                  serviceId: z97bsc1rlv9qbk3
                  scope: instance
                  source: workflow
                  eventKey: lead.created
                  title: Nova solicitação de contato!
                  description: Novo lead recebido pelo ConectAI
                  body: "Nova solicitação de contato: Marina Costa - ConectAI Enterprise"
                  bodyType: text
                  icon: i-lucide-user-round-plus
                  priority: high
                  requireAction: true
                  entityType: lead
                  entityId: lead-template-test-001
                  dedupeKey: lead:conectai-enterprise:teste-001
                  groupKey: lead:conectai-enterprise
                  channels:
                    - whatsapp
                    - email
                  audience:
                    roles:
                      - super_admin
                      - gestor
                      - notificacoes
                  payload:
                    whatsapp:
                      templateName: novo_contato_02
                      category: UTILITY
                      language: pt_BR
                      content: Nova solicitação de contato!
                      processedParams:
                        body:
                          "1": Time Comercial
                          "2": Marina Costa
                          "3": marina.costa.teste@example.com
                          "4": "+55 11 98888-7777"
                          "5": ConectAI Enterprise
                          "6": Lead fictício para teste de templates.
                    email:
                      subject: Nova solicitação Ailian
                      html: <p><strong>Novo lead recebido</strong></p><p>Marina Costa demonstrou interesse em ConectAI Enterprise.</p>
                      text: Novo lead recebido - Marina Costa demonstrou interesse em ConectAI Enterprise.
      responses:
        "200":
          description: Duplicate request matched an existing notification
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreateNotificationResponse'
              examples:
                duplicate:
                  value:
                    status: duplicate
                    notificationId: abc123dupnotif0
                    navigationKey: connectai-lead-assigned-41988756111
                    route: /dashboard/notifications/r/connectai-lead-assigned-41988756111?copy=body
                    recipientIds:
                      - rcpt123dup00001
                    deliveryIds:
                      - dlv123dup000001
                    suppressedRecipients: []
        "201":
          description: Notification accepted and persisted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreateNotificationResponse'
              examples:
                created:
                  value:
                    status: created
                    notificationId: abc123newnotif01
                    navigationKey: connectai-lead-assigned-41988756111
                    route: /dashboard/notifications/r/connectai-lead-assigned-41988756111?copy=body
                    recipientIds:
                      - rcpt123new00001
                    deliveryIds:
                      - dlv123new000001
                      - dlv123new000002
                    suppressedRecipients: []
                suppressed:
                  value:
                    status: suppressed
                    notificationId: abc123supnotif01
                    navigationKey: connectai-lead-assigned-41988756111
                    route: /dashboard/notifications/r/connectai-lead-assigned-41988756111?copy=body
                    recipientIds: []
                    deliveryIds: []
                    suppressedRecipients:
                      - userId: y1412767403q7jx
                        channels:
                          - push
                        reason: preferences
                        preference:
                          scope_key: instance:4f0kfmefs6x09lo
                          toast_enable: false
                          push_enabled: false
        "401":
          description: Missing or invalid bearer token
  /internal/reminders/scan:
    post:
      tags: [internal]
      summary: Queue reminder scan
      security:
        - bearerAuth: []
      responses:
        "202":
          description: Scan job queued
  /internal/reminders/{id}/enqueue:
    post:
      tags: [internal]
      summary: Enqueue a reminder delivery
      security:
        - bearerAuth: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
      responses:
        "202":
          description: Reminder delivery queued
  /internal/reminder-deliveries/{id}/retry:
    post:
      tags: [internal]
      summary: Retry a reminder delivery
      security:
        - bearerAuth: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
      responses:
        "202":
          description: Reminder delivery retry queued
  /internal/notification-deliveries/{id}/retry:
    post:
      tags: [internal]
      summary: Retry a notification delivery
      security:
        - bearerAuth: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
      responses:
        "202":
          description: Notification delivery retry queued
  /internal/push/test:
    post:
      tags: [internal]
      summary: Send test push
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PushTestRequest'
            examples:
              byUser:
                value:
                  userId: y1412767403q7jx
                  title: Teste manual
                  message: Push direto pelo endpoint interno
                  provider: webpush
                  payload:
                    debug: true
      responses:
        "200":
          description: Test push sent
        "502":
          description: Provider failed
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: token
  schemas:
    NotificationChannel:
      type: string
      enum: [realtime, push, email, whatsapp, webhook]
    NotificationProvider:
      type: string
      enum: [pocketbase, webpush, fcm, apns, resend, chatwoot, n8n]
    LocalEmailTemplateName:
      type: string
      description: Nome de template local versionado na repo.
      enum: [payment-approved, invoice-issued]
    EmailTemplateVariableSpec:
      type: object
      required: [name, required, description]
      properties:
        name:
          type: string
        required:
          type: boolean
        description:
          type: string
        example:
          type: string
    EmailTemplateCatalogItem:
      type: object
      required: [name, label, description, variables, examplePayload]
      properties:
        name:
          $ref: '#/components/schemas/LocalEmailTemplateName'
        label:
          type: string
        description:
          type: string
        variables:
          type: array
          items:
            $ref: '#/components/schemas/EmailTemplateVariableSpec'
        examplePayload:
          type: object
          additionalProperties: true
    EmailTemplateCatalogResponse:
      type: object
      required: [templates]
      properties:
        templates:
          type: array
          items:
            $ref: '#/components/schemas/EmailTemplateCatalogItem'
    UserInstanceRole:
      type: string
      enum: [super_admin, gestor, comercial, marketing, especialista, agente, notificacoes]
    NotificationRecipientInput:
      type: object
      required: [userId]
      properties:
        userId:
          type: string
        channels:
          type: array
          items:
            $ref: '#/components/schemas/NotificationChannel'
    NotificationAudienceInput:
      type: object
      properties:
        roles:
          type: array
          description: >
            Filtra usuarios por role em user_instance_roles. Para scope=instance,
            tambem filtra pelo instanceId informado. Para scope=application,
            considera todos os vinculos de instancia.
          items:
            $ref: '#/components/schemas/UserInstanceRole'
    CreateNotificationRequest:
      type: object
      required: [scope, title, priority, channels]
      properties:
        instanceId:
          type: string
          example: 4f0kfmefs6x09lo
        serviceId:
          type: string
          example: z97bsc1rlv9qbk3
        createdBy:
          type: string
        scope:
          type: string
          enum: [application, instance, user]
        source:
          type: string
          enum: [system, manual, workflow, api]
          default: api
        eventKey:
          type: string
        title:
          type: string
        description:
          type: string
        body:
          type: string
        bodyType:
          type: string
          enum: [text, html]
          default: text
        icon:
          type: string
        priority:
          type: string
          enum: [low, normal, high, urgent]
        requireAction:
          type: boolean
          default: false
        navigationKey:
          type: string
        route:
          type: string
          description: >
            Campo aceito por compatibilidade, mas a rota persistida e derivada
            pelo backend como /dashboard/notifications/r/<navigationKey>.
        entityType:
          type: string
        entityId:
          type: string
        actions:
          type: array
          items:
            type: object
            additionalProperties: true
        payload:
          type: object
          description: >
            Payload livre por canal/provider. Para email local versionado,
            use payload.email.template com um valor de LocalEmailTemplateName
            e payload.email.variables com as variáveis do template.
          additionalProperties: true
        dedupeKey:
          type: string
        idempotencyKey:
          type: string
        groupKey:
          type: string
        scheduledFor:
          type: string
          format: date-time
        expiresAt:
          type: string
          format: date-time
        channels:
          type: array
          items:
            $ref: '#/components/schemas/NotificationChannel'
        providers:
          type: object
          additionalProperties:
            $ref: '#/components/schemas/NotificationProvider'
        audience:
          $ref: '#/components/schemas/NotificationAudienceInput'
        recipients:
          type: array
          description: >
            Destinatarios explicitos. Obrigatorio para scope=user. Para
            scope=instance ou scope=application, pode ser omitido quando
            audience for usado para expandir usuarios via user_instance_roles.
          items:
            $ref: '#/components/schemas/NotificationRecipientInput'
    CreateNotificationResponse:
      type: object
      properties:
        status:
          type: string
          enum: [created, duplicate, suppressed]
        notificationId:
          type: string
        navigationKey:
          type: string
        route:
          type: string
        recipientIds:
          type: array
          items:
            type: string
        deliveryIds:
          type: array
          items:
            type: string
        suppressedRecipients:
          type: array
          items:
            type: object
            properties:
              userId:
                type: string
              channels:
                type: array
                items:
                  $ref: '#/components/schemas/NotificationChannel'
              reason:
                type: string
                enum: [preferences, no_channels]
              preference:
                type: object
                additionalProperties: true
    PushTestRequest:
      type: object
      properties:
        userId:
          type: string
          example: y1412767403q7jx
        deviceId:
          type: string
        title:
          type: string
        message:
          type: string
        provider:
          type: string
        payload:
          type: object
          additionalProperties: true
      additionalProperties: true
