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

# Programar mensaje en una conversación

> Programa un mensaje (AI o humano) en una conversación.

**Ramas:**

- `executor_kind: 'ai_employee'` — al firing, el AI procesará la conversación con el LLM y enviará la respuesta. El fallback template es opcional (si la ventana 24h está cerrada al firing, el firing usa el default del canal o el que pasaste).
- `executor_kind: 'human_user'` — al firing, el sistema envía el payload literal que armaste (texto/media/QR/template). **El `template_uuid` es obligatorio** porque la ventana 24h puede cerrar antes del firing y la API pública no puede asumir un default.

**Replace:** si la conversación ya tiene un schedule pending, este POST lo cancela y crea uno nuevo (atómico). Solo se permite 1 pending por conversación.

**Archivos:** para media (mensaje principal o header de template), envía `file_uuid` de un archivo previamente subido vía `/files/request-upload` + `/files/confirm` con `module=api_messages`. El servidor genera signed URLs y las re-firma al disparar.

**Audit:** la API key queda persistida en `scheduled_messages.api_key_id`. El `author_snapshot` reporta `type='api_token'` con el UUID público del token.



## OpenAPI

````yaml /openapi.json post /{slug}/conversation/scheduled-message
openapi: 3.1.0
info:
  title: 1TO1 AI Public API
  version: 0.2.0
  description: >-
    API pública de 1TO1 AI para integraciones externas (n8n, CRMs, scripts).
    Auth via Bearer token. Rate limit 60 req/min por API key. Contrato REST
    puro: el body de éxito es el payload directo (status HTTP como señal),
    errores retornan `{code, message}` con ambos campos garantizados. Ver schema
    `PublicError`.
servers:
  - url: https://app.1to1.ai/api/v1/public
    description: Producción
security:
  - bearerAuth: []
paths:
  /{slug}/conversation/scheduled-message:
    post:
      tags:
        - Schedules
      summary: Programar mensaje en una conversación
      description: >-
        Programa un mensaje (AI o humano) en una conversación.


        **Ramas:**


        - `executor_kind: 'ai_employee'` — al firing, el AI procesará la
        conversación con el LLM y enviará la respuesta. El fallback template es
        opcional (si la ventana 24h está cerrada al firing, el firing usa el
        default del canal o el que pasaste).

        - `executor_kind: 'human_user'` — al firing, el sistema envía el payload
        literal que armaste (texto/media/QR/template). **El `template_uuid` es
        obligatorio** porque la ventana 24h puede cerrar antes del firing y la
        API pública no puede asumir un default.


        **Replace:** si la conversación ya tiene un schedule pending, este POST
        lo cancela y crea uno nuevo (atómico). Solo se permite 1 pending por
        conversación.


        **Archivos:** para media (mensaje principal o header de template), envía
        `file_uuid` de un archivo previamente subido vía `/files/request-upload`
        + `/files/confirm` con `module=api_messages`. El servidor genera signed
        URLs y las re-firma al disparar.


        **Audit:** la API key queda persistida en
        `scheduled_messages.api_key_id`. El `author_snapshot` reporta
        `type='api_token'` con el UUID público del token.
      parameters:
        - $ref: '#/components/parameters/SlugPathParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SchedulePostBody'
            examples:
              aiSimple:
                summary: AI por teléfono (la natural key del CRM)
                value:
                  executor_kind: ai_employee
                  conversation:
                    phone: +52 55 1234 5678
                  schedule_at:
                    date: '2026-06-15'
                    time: '14:30'
                  reason: Seguimiento del cliente tras la propuesta
              humanText:
                summary: Humano con texto + fallback template (usa BSUID)
                value:
                  executor_kind: human_user
                  conversation:
                    whatsapp_user_id: MX.1A2B3C4D5E
                  schedule_at:
                    date: '2026-06-15'
                    time: '14:30'
                  message_kind: message
                  payload:
                    kind: message
                    text: ¿Pudiste ver la propuesta?
                  template_uuid: aaaa1111-2222-3333-4444-555566667777
              humanTemplateWithMediaHeader:
                summary: Humano enviando template con header image
                value:
                  executor_kind: human_user
                  conversation:
                    phone: +52 55 1234 5678
                  schedule_at:
                    date: '2026-06-15'
                    time: '14:30'
                  message_kind: template
                  payload:
                    kind: template
                    template_uuid: aaaa1111-2222-3333-4444-555566667777
                    variables:
                      - index: 1
                        value: Ana
                    header:
                      type: image
                      file_uuid: bbbb2222-3333-4444-5555-666677778888
                  template_uuid: cccc3333-4444-5555-6666-777788889999
      responses:
        '201':
          description: Schedule creado.
          headers:
            X-Request-Id:
              schema:
                type: string
                format: uuid
                description: >-
                  ID único para correlacionar con logs server-side. Si el
                  cliente envía uno válido (UUID), se preserva; si no, se genera
                  uno nuevo.
              required: true
              description: >-
                ID único para correlacionar con logs server-side. Si el cliente
                envía uno válido (UUID), se preserva; si no, se genera uno
                nuevo.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SchedulePostResponse'
        '400':
          description: >-
            Body inválido (INVALID_REQUEST). Cubre además: schedule en el
            pasado, schedule fuera del rango de QStash (máx ~1 año),
            `template_uuid` faltante en rama humana, variables del template
            inválidas.
          headers:
            X-Request-Id:
              schema:
                type: string
                format: uuid
                description: >-
                  ID único para correlacionar con logs server-side. Si el
                  cliente envía uno válido (UUID), se preserva; si no, se genera
                  uno nuevo.
              required: true
              description: >-
                ID único para correlacionar con logs server-side. Si el cliente
                envía uno válido (UUID), se preserva; si no, se genera uno
                nuevo.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicError'
        '401':
          description: Token ausente, mal formado o no válido (INVALID_API_TOKEN)
          headers:
            X-Request-Id:
              schema:
                type: string
                format: uuid
                description: >-
                  ID único para correlacionar con logs server-side. Si el
                  cliente envía uno válido (UUID), se preserva; si no, se genera
                  uno nuevo.
              required: true
              description: >-
                ID único para correlacionar con logs server-side. Si el cliente
                envía uno válido (UUID), se preserva; si no, se genera uno
                nuevo.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicError'
        '403':
          description: >-
            El slug del path no coincide con el business del token
            (TOKEN_BUSINESS_MISMATCH)
          headers:
            X-Request-Id:
              schema:
                type: string
                format: uuid
                description: >-
                  ID único para correlacionar con logs server-side. Si el
                  cliente envía uno válido (UUID), se preserva; si no, se genera
                  uno nuevo.
              required: true
              description: >-
                ID único para correlacionar con logs server-side. Si el cliente
                envía uno válido (UUID), se preserva; si no, se genera uno
                nuevo.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicError'
        '404':
          description: >-
            Conversación no resuelve (CONVERSATION_NOT_FOUND) / AI employee no
            encontrado (AI_EMPLOYEE_NOT_FOUND) / template fallback no encontrado
            (TEMPLATE_NOT_FOUND) / archivo del header inexistente o no
            `confirmed` (FILE_NOT_FOUND) / quick reply no encontrada
            (QUICK_REPLY_NOT_FOUND).
          headers:
            X-Request-Id:
              schema:
                type: string
                format: uuid
                description: >-
                  ID único para correlacionar con logs server-side. Si el
                  cliente envía uno válido (UUID), se preserva; si no, se genera
                  uno nuevo.
              required: true
              description: >-
                ID único para correlacionar con logs server-side. Si el cliente
                envía uno válido (UUID), se preserva; si no, se genera uno
                nuevo.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicError'
        '409':
          description: >-
            El identificador de conversación es ambiguo: varios contactos
            comparten el teléfono (`PHONE_NUMBER_AMBIGUOUS`) o el username
            (`USERNAME_AMBIGUOUS`). Identifica la conversación por
            `whatsapp_user_id` o `uuid`.
          headers:
            X-Request-Id:
              schema:
                type: string
                format: uuid
                description: >-
                  ID único para correlacionar con logs server-side. Si el
                  cliente envía uno válido (UUID), se preserva; si no, se genera
                  uno nuevo.
              required: true
              description: >-
                ID único para correlacionar con logs server-side. Si el cliente
                envía uno válido (UUID), se preserva; si no, se genera uno
                nuevo.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicError'
        '422':
          description: >-
            Rama AI sin AI asignado en la conversación y sin `ai_employee_uuid`
            en el body (`NO_AI_EMPLOYEE_ASSIGNED`) / rama humana exige fallback
            cuando la ventana está cerrada al programar (`WINDOW_CLOSED`) /
            **owner mismatch** (`OWNER_MISMATCH`): schedule humano sobre conv
            con AI asignado, o AI schedule con `ai_employee_uuid` distinto al
            asignado a la conv. La regla es: AI schedules requieren AI assigned
            + matching; human schedules requieren conv SIN AI assigned. Para
            cambiar el owner antes de programar usa `POST
            /conversation/ai-employee` o `POST /conversation/human`.
          headers:
            X-Request-Id:
              schema:
                type: string
                format: uuid
                description: >-
                  ID único para correlacionar con logs server-side. Si el
                  cliente envía uno válido (UUID), se preserva; si no, se genera
                  uno nuevo.
              required: true
              description: >-
                ID único para correlacionar con logs server-side. Si el cliente
                envía uno válido (UUID), se preserva; si no, se genera uno
                nuevo.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicError'
        '429':
          description: >-
            Rate limit excedido (RATE_LIMIT_EXCEEDED). Headers `Retry-After`,
            `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`
            indican cuándo reintentar.
          headers:
            X-Request-Id:
              schema:
                type: string
                format: uuid
                description: >-
                  ID único para correlacionar con logs server-side. Si el
                  cliente envía uno válido (UUID), se preserva; si no, se genera
                  uno nuevo.
              required: true
              description: >-
                ID único para correlacionar con logs server-side. Si el cliente
                envía uno válido (UUID), se preserva; si no, se genera uno
                nuevo.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicError'
        '500':
          description: Error inesperado del servidor (UNKNOWN_ERROR)
          headers:
            X-Request-Id:
              schema:
                type: string
                format: uuid
                description: >-
                  ID único para correlacionar con logs server-side. Si el
                  cliente envía uno válido (UUID), se preserva; si no, se genera
                  uno nuevo.
              required: true
              description: >-
                ID único para correlacionar con logs server-side. Si el cliente
                envía uno válido (UUID), se preserva; si no, se genera uno
                nuevo.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicError'
components:
  parameters:
    SlugPathParam:
      schema:
        $ref: '#/components/schemas/SlugPathParam'
      required: true
      description: >-
        Slug del business, case-insensitive. Debe coincidir con el business del
        token.
      name: slug
      in: path
  schemas:
    SchedulePostBody:
      oneOf:
        - type: object
          properties:
            executor_kind:
              type: string
              enum:
                - ai_employee
            conversation:
              $ref: '#/components/schemas/ConversationRef'
            scheduled_uuid:
              type: string
              format: uuid
              description: >-
                UUID pre-generado client-side (opcional). Reservado para uso
                futuro — el flow de pre-subir archivos referenciando el UUID
                antes del POST todavía no está disponible en API pública (sí en
                dashboard). Si lo omites, el servidor genera el UUID
                automáticamente; si lo envías, se respeta vía COALESCE en el
                RPC.
            schedule_at:
              $ref: '#/components/schemas/PublicScheduleAtInput'
            reason:
              type: string
              minLength: 1
              maxLength: 500
              description: >-
                Motivo del programado. Se inyecta como context_note en la
                conversación al firing.
            ai_employee_uuid:
              type:
                - string
                - 'null'
              format: uuid
              description: >-
                UUID del AI employee. Si null/omitido, usa el AI asignado a la
                conversación. Si la conv no tiene AI asignado y no lo pasas,
                falla con `NO_AI_EMPLOYEE_ASSIGNED`.
            template_uuid:
              type:
                - string
                - 'null'
              format: uuid
              description: >-
                Template fallback. Si omitido, el firing usa el default
                scheduled template del canal (auto-creado al configurar
                WhatsApp).
          required:
            - executor_kind
            - conversation
            - schedule_at
            - reason
        - type: object
          properties:
            executor_kind:
              type: string
              enum:
                - human_user
            conversation:
              $ref: '#/components/schemas/ConversationRef'
            scheduled_uuid:
              type: string
              format: uuid
              description: >-
                UUID pre-generado client-side (opcional). Reservado para uso
                futuro — el flow de pre-subir archivos referenciando el UUID
                antes del POST todavía no está disponible en API pública (sí en
                dashboard). Si lo omites, el servidor genera el UUID
                automáticamente; si lo envías, se respeta vía COALESCE en el
                RPC.
            schedule_at:
              $ref: '#/components/schemas/PublicScheduleAtInput'
            message_kind:
              type: string
              enum:
                - quick_reply
                - template
                - message
            payload:
              $ref: '#/components/schemas/ScheduledHumanPayload'
            template_uuid:
              type: string
              format: uuid
              description: >-
                OBLIGATORIO en API pública (rama humana): la ventana 24h puede
                cerrar antes del firing y el envío fuera de ventana requiere un
                template. Sin este campo el POST retorna `INVALID_REQUEST` con
                código interno `TEMPLATE_REQUIRED`.
            template_variables:
              type: array
              items:
                $ref: '#/components/schemas/ScheduledTemplateVariable'
            template_header:
              $ref: '#/components/schemas/ScheduledTemplateHeaderInput'
            template_button_parameters:
              type: array
              items:
                $ref: '#/components/schemas/ScheduledTemplateButtonParameter'
          required:
            - executor_kind
            - conversation
            - schedule_at
            - message_kind
            - payload
            - template_uuid
    SchedulePostResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            uuid:
              type: string
              format: uuid
            executor_kind:
              type: string
              enum:
                - ai_employee
                - human_user
            schedule_at:
              type: string
              format: date-time
            status:
              type: string
              example: pending
          required:
            - uuid
            - executor_kind
            - schedule_at
            - status
      required:
        - data
    PublicError:
      type: object
      properties:
        code:
          type: string
          example: INVALID_API_TOKEN
        message:
          type: string
          example: >-
            The provided API token is invalid or has been revoked. Generate a
            new one in Settings → API Token.
      required:
        - code
        - message
      description: >-
        Public API error body. The client should match against `code` (stable
        canonical identifier) and NOT against `message` (human text, may be
        refined between API versions without notice). The HTTP status code is
        the error-type signal (4xx client, 5xx server).
    SlugPathParam:
      type: string
      example: acme
      description: >-
        Slug del business, case-insensitive. Debe coincidir con el business del
        token.
    ConversationRef:
      type: object
      properties:
        uuid:
          type: string
          format: uuid
          description: >-
            UUID interno de 1TO1 AI. Fallback — los CRMs externos normalmente no
            lo conocen.
        whatsapp_user_id:
          type: string
          description: >-
            BSUID de WhatsApp (identidad opaca de Meta). Se resuelve por
            igualdad exacta contra el identificador BSUID del contacto (no se
            normaliza).
        username:
          type: string
          example: '@mariana.lopez'
          description: >-
            Username público de WhatsApp del contacto. Se acepta con o sin `@`
            inicial y sin distinguir mayúsculas (match case-insensitive contra
            el último username observado por la plataforma; best-effort: el
            usuario puede cambiarlo — la clave estable es `uuid`). Si varios
            contactos lo comparten, la API responde 409 `USERNAME_AMBIGUOUS`.
        phone:
          type: string
          example: +52 55 1234 5678
          description: >-
            Teléfono en formato E.164. Se normaliza (solo dígitos) antes del
            lookup por teléfono del contacto. Puede estar duplicado entre
            contactos (números reciclados / identidades separadas): en ese caso
            la API responde 409 `PHONE_NUMBER_AMBIGUOUS` en vez de adivinar.
      description: >-
        Identificador de la conversación — exactamente UNO de los cuatro campos
        debe venir. Orden de resolución: uuid > whatsapp_user_id > username >
        phone. El lookup filtra por el business del token (defense in depth
        cross-tenant). Resolver por `phone` o `username` puede devolver 409
        (`PHONE_NUMBER_AMBIGUOUS` / `USERNAME_AMBIGUOUS`) cuando varios
        contactos comparten el identificador — aplica a cualquier endpoint que
        acepte esta referencia.
    PublicScheduleAtInput:
      type: object
      properties:
        date:
          type: string
          pattern: ^\d{4}-\d{2}-\d{2}$
          example: '2026-06-15'
          description: Fecha local en formato ISO 8601 `YYYY-MM-DD`.
        time:
          type: string
          pattern: ^\d{2}:\d{2}$
          example: '14:30'
          description: >-
            Hora local en formato 24h `HH:MM`. Validamos rangos: hora 00–23,
            minuto 00–59.
      required:
        - date
        - time
      description: >-
        Fecha + hora **interpretadas en la zona horaria del negocio**
        (`businesses.timezone`, configurada al onboarding — típicamente
        `America/Mexico_City`). El servidor combina ambos campos contra esa zona
        para construir el instante UTC. La response normaliza a ISO `Z` (UTC).
    ScheduledHumanPayload:
      oneOf:
        - type: object
          properties:
            kind:
              type: string
              enum:
                - quick_reply
            quick_reply_uuid:
              type: string
              format: uuid
              description: UUID de la Quick Reply del business.
          required:
            - kind
            - quick_reply_uuid
        - type: object
          properties:
            kind:
              type: string
              enum:
                - template
            template_uuid:
              type: string
              format: uuid
            variables:
              type: array
              items:
                $ref: '#/components/schemas/ScheduledTemplateVariable'
            header:
              $ref: '#/components/schemas/ScheduledTemplateHeaderInput'
            button_parameters:
              type: array
              items:
                $ref: '#/components/schemas/ScheduledTemplateButtonParameter'
          required:
            - kind
            - template_uuid
            - variables
        - type: object
          properties:
            kind:
              type: string
              enum:
                - message
            text:
              type:
                - string
                - 'null'
              minLength: 1
              maxLength: 4096
            media:
              type:
                - object
                - 'null'
              properties:
                file_uuid:
                  type: string
                  format: uuid
                  description: >-
                    UUID de un archivo previamente subido vía
                    `/files/request-upload` + `/files/confirm`.
                transcription:
                  type:
                    - string
                    - 'null'
                  maxLength: 4096
                  description: >-
                    Transcripción del audio/video (opcional en la API pública).
                    La obligatoriedad universal llegará en v0.3.0.
              required:
                - file_uuid
          required:
            - kind
      description: Payload del schedule humano. Discriminado por `kind`.
    ScheduledTemplateVariable:
      type: object
      properties:
        index:
          type: integer
          minimum: 1
          example: 1
        value:
          type: string
          minLength: 1
          example: Ana
      required:
        - index
        - value
    ScheduledTemplateHeaderInput:
      oneOf:
        - type: object
          properties:
            type:
              type: string
              enum:
                - text
            value:
              type: string
              minLength: 1
          required:
            - type
            - value
        - type: object
          properties:
            type:
              type: string
              enum:
                - image
            file_uuid:
              type: string
              format: uuid
          required:
            - type
            - file_uuid
        - type: object
          properties:
            type:
              type: string
              enum:
                - video
            file_uuid:
              type: string
              format: uuid
          required:
            - type
            - file_uuid
        - type: object
          properties:
            type:
              type: string
              enum:
                - document
            file_uuid:
              type: string
              format: uuid
          required:
            - type
            - file_uuid
      description: >-
        Header del template. Para `text`, envía `value`. Para
        `image`/`video`/`document`, envía `file_uuid` de un archivo previamente
        subido vía `/files/request-upload` + `/files/confirm` con
        `module=api_messages`. El servidor genera la signed URL al programar y
        la re-firma al disparar (preserva validez si el schedule fue programado
        a 30+ días).
    ScheduledTemplateButtonParameter:
      type: object
      properties:
        index:
          type: integer
          minimum: 0
          example: 0
        sub_type:
          type: string
          enum:
            - url
        value:
          type: string
          minLength: 1
          example: promo-2026
      required:
        - index
        - sub_type
        - value
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: sk_1to1_<hex>
      description: >-
        API token emitido desde Settings → API Token del dashboard 1TO1 AI.
        Enviar en header Authorization: Bearer sk_1to1_...

````