> ## Documentation Index
> Fetch the complete documentation index at: https://docs.visitoai.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Send follow up

> Requires `crm:followups:send`. Explicit outbound send. Uses the existing follow-up claim and durable send key; do not create another follow-up to retry. A concurrent or completed claim can return 409. crmStatusUpdateFailed means sending was accepted but CRM completion failed; reconcile the reply receipt rather than sending again.



## OpenAPI

````yaml /openapi.json post /crm/follow-ups/{followUpId}/send
openapi: 3.0.3
info:
  title: Visito M2M API
  version: 1.0.0
  description: >-
    Tenant-scoped server-to-server API for channels, conversations, commerce,
    custom AI tools, and WhatsApp templates. Send operations are asynchronous
    and return queued acknowledgements. Expansion adds conversation controls,
    knowledge, sales opportunities, delivery status, reporting, and signed
    webhooks.
servers:
  - url: https://platform-api.visitoai.com/m2m/v1
    description: Production
security:
  - bearerAuth: []
tags:
  - name: Channels
  - name: Conversations
  - name: Commerce
  - name: Custom tools
  - name: WhatsApp templates
  - name: Properties
  - name: Knowledge
  - name: Sales CRM
  - name: Delivery
  - name: Reporting
  - name: Webhooks
paths:
  /crm/follow-ups/{followUpId}/send:
    post:
      summary: Send follow up
      description: >-
        Requires `crm:followups:send`. Explicit outbound send. Uses the existing
        follow-up claim and durable send key; do not create another follow-up to
        retry. A concurrent or completed claim can return 409.
        crmStatusUpdateFailed means sending was accepted but CRM completion
        failed; reconcile the reply receipt rather than sending again.
      operationId: send_follow_up
      parameters:
        - name: followUpId
          in: path
          required: true
          schema:
            type: string
            maxLength: 200
            minLength: 1
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                messageText:
                  type: string
                  maxLength: 1200
              additionalProperties: false
      responses:
        '202':
          description: Accepted and queued, not delivered
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicFollowUpSendResult'
        default:
          $ref: '#/components/responses/Error'
components:
  schemas:
    PublicFollowUpSendResult:
      type: object
      properties:
        followUp:
          $ref: '#/components/schemas/PublicFollowUp'
        reply:
          $ref: '#/components/schemas/QueuedReply'
        crmStatusUpdateFailed:
          type: boolean
      required:
        - followUp
        - reply
      additionalProperties: false
    PublicFollowUp:
      type: object
      properties:
        followUpId:
          type: string
          maxLength: 2000
        opportunityId:
          type: string
          maxLength: 2000
        leadId:
          type: string
          maxLength: 2000
        conversationId:
          type: string
          maxLength: 2000
        channel:
          type: string
          maxLength: 2000
        status:
          type: string
          maxLength: 2000
        mode:
          type: string
          maxLength: 2000
        reasonCode:
          type: string
          maxLength: 2000
        reasonText:
          type: string
          maxLength: 2000
        reason:
          type: string
          maxLength: 2000
        messageDraft:
          type: string
          maxLength: 2000
        draftStatus:
          type: string
          maxLength: 2000
        dueAt:
          type: string
          maxLength: 2000
        expiresAt:
          type: string
          maxLength: 2000
        sentAt:
          type: string
          maxLength: 2000
        replyId:
          type: string
          maxLength: 2000
        createdAt:
          type: string
          maxLength: 2000
        updatedAt:
          type: string
          maxLength: 2000
      required:
        - followUpId
        - leadId
        - status
        - mode
      additionalProperties: false
    QueuedReply:
      type: object
      required:
        - accepted
        - replyId
        - conversationId
        - requestEventId
        - correlationId
        - status
        - acceptedAt
      properties:
        accepted:
          type: boolean
          enum:
            - true
        replyId:
          type: string
        conversationId:
          type: string
        requestEventId:
          type: string
        correlationId:
          type: string
        status:
          type: string
          enum:
            - queued
        acceptedAt:
          type: string
          format: date-time
    ErrorResponse:
      type: object
      required:
        - error
      properties:
        error:
          type: object
          required:
            - code
            - message
          properties:
            code:
              type: string
              example: M2M_AUTH_INSUFFICIENT_SCOPE
            message:
              type: string
            details:
              type: object
              additionalProperties: true
      example:
        error:
          code: M2M_AUTH_INSUFFICIENT_SCOPE
          message: M2M credential does not have required scope.
          details:
            requiredScopes:
              - conversations:write
            missingScopes:
              - conversations:write
  responses:
    Error:
      description: Structured error
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: Visito M2M API key
      description: Server-side tenant-scoped credential created from Build > API Keys.

````