> ## 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.

# Enviar template (reabre ventana 24h)

> Envía un template de WhatsApp ya aprobado por Meta. **Reabre la ventana de 24h** — se puede usar incluso si el cliente no ha interactuado recientemente. Este es el único tipo de mensaje que puedes enviar fuera de la ventana.

**Resolución por nombre:**

Se envía por `template_name`, no por UUID. Meta + nuestra DB enforzan unicidad de nombre por business. Si un template existe en múltiples idiomas (mismo nombre), especifica `language` (`es_MX`, `en_US`, etc.) — si no lo haces y hay ambigüedad la respuesta es `TEMPLATE_AMBIGUOUS` (409).

> **Nota multi-canal:** el unique real en DB es `(channel_configuration_id, meta_template_id, language)`. Si un business tiene 2+ canales WhatsApp con el mismo `(name, language)`, la API responde `TEMPLATE_AMBIGUOUS` sin forma de desempate vía contrato público (este endpoint no expone `channel_configuration_uuid`). El servidor loguea un warn observable. Si necesitas enviar por API pública a un business multi-canal con templates duplicados, abrir issue y evaluamos ampliar el contrato.

**Placeholders:**

- `body_variables` — array con `{index, value}` para llenar `{{1}}`, `{{2}}`, … del body. Index es 1-based y debe estar alineado con el template original.
- `header` — si el template tiene header, se pasa aquí. Para media, usa `file_uuid` de un archivo ya confirmed (ver endpoints de `/files/*`). El servidor genera la signed URL antes de enviar a Meta.
- `button_parameters` — solo para botones URL con placeholder dinámico (`{{1}}` en la URL). Los quick-reply buttons del template no llevan parámetros.

**Guards aplicados:**

- Ventana 24h — NO aplica. El template la reabre.
- Wallet general — NO aplica (paridad con el envío manual del dashboard).
- Wallet visión — aplica si el header es `image` / `video` / `document` (el AI podría necesitarla para procesar respuestas que referencien el contenido).



## OpenAPI

````yaml /openapi.json post /{slug}/conversation/messages/template
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/messages/template:
    post:
      tags:
        - Messages
      summary: Enviar template (reabre ventana 24h)
      description: >-
        Envía un template de WhatsApp ya aprobado por Meta. **Reabre la ventana
        de 24h** — se puede usar incluso si el cliente no ha interactuado
        recientemente. Este es el único tipo de mensaje que puedes enviar fuera
        de la ventana.


        **Resolución por nombre:**


        Se envía por `template_name`, no por UUID. Meta + nuestra DB enforzan
        unicidad de nombre por business. Si un template existe en múltiples
        idiomas (mismo nombre), especifica `language` (`es_MX`, `en_US`, etc.) —
        si no lo haces y hay ambigüedad la respuesta es `TEMPLATE_AMBIGUOUS`
        (409).


        > **Nota multi-canal:** el unique real en DB es
        `(channel_configuration_id, meta_template_id, language)`. Si un business
        tiene 2+ canales WhatsApp con el mismo `(name, language)`, la API
        responde `TEMPLATE_AMBIGUOUS` sin forma de desempate vía contrato
        público (este endpoint no expone `channel_configuration_uuid`). El
        servidor loguea un warn observable. Si necesitas enviar por API pública
        a un business multi-canal con templates duplicados, abrir issue y
        evaluamos ampliar el contrato.


        **Placeholders:**


        - `body_variables` — array con `{index, value}` para llenar `{{1}}`,
        `{{2}}`, … del body. Index es 1-based y debe estar alineado con el
        template original.

        - `header` — si el template tiene header, se pasa aquí. Para media, usa
        `file_uuid` de un archivo ya confirmed (ver endpoints de `/files/*`). El
        servidor genera la signed URL antes de enviar a Meta.

        - `button_parameters` — solo para botones URL con placeholder dinámico
        (`{{1}}` en la URL). Los quick-reply buttons del template no llevan
        parámetros.


        **Guards aplicados:**


        - Ventana 24h — NO aplica. El template la reabre.

        - Wallet general — NO aplica (paridad con el envío manual del
        dashboard).

        - Wallet visión — aplica si el header es `image` / `video` / `document`
        (el AI podría necesitarla para procesar respuestas que referencien el
        contenido).
      parameters:
        - $ref: '#/components/parameters/SlugPathParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SendTemplateBody'
            examples:
              textHeader:
                summary: Template con variables en body
                value:
                  conversation:
                    phone: '+525512345678'
                  template_name: confirmacion_cita
                  body_variables:
                    - index: 1
                      value: Ana
                    - index: 2
                      value: 10:00 AM
              imageHeader:
                summary: Template con header image (file_uuid)
                value:
                  conversation:
                    phone: '+525512345678'
                  template_name: promo_verano
                  language: es_MX
                  header:
                    type: image
                    file_uuid: 9d8c7b6a-5432-1098-7654-321abcdef012
                  body_variables:
                    - index: 1
                      value: 20%
      responses:
        '201':
          description: Template enviado. `wamid` es el ID de Meta.
          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/SendTemplateResponse'
        '400':
          description: >-
            Body inválido (INVALID_REQUEST) / variables no válidas
            (TEMPLATE_VARIABLES_INVALID) / header del request no matchea el
            formato del template aprobado en Meta (TEMPLATE_HEADER_MISMATCH —
            preflight detecta el mismatch antes de llegar a Meta).
          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'
        '402':
          description: >-
            Wallet de visión bloqueado (VISION_WALLET_BLOCKED). Solo si el
            header es media.
          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) / contacto
            asociado no encontrado (CONTACT_NOT_FOUND, race entre el resolve y
            la fase) / archivo del header inexistente (FILE_NOT_FOUND) /
            template no existe (TEMPLATE_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: >-
            `TEMPLATE_AMBIGUOUS`: dos casos. (a) El mismo `template_name` existe
            en múltiples idiomas para el business y el request no incluye
            `language` — pasar `language` para desempatar. (b) Un business
            multi-canal tiene el mismo `(template_name, language)` en 2+ canales
            WhatsApp (cada uno con `meta_template_id` propio en su WABA, todas
            válidas) — sin desempate posible desde el contrato actual (no expone
            `channel_uuid`). En este caso el servidor loguea un warn observable.
            La ambigüedad cross-set FAKE↔APPROVED al enviar a una conversación
            tester NO emite este código: el resolver prefiere la FAKE por
            defecto (la creación de fakes ya rechaza colisión con APPROVED del
            mismo (name, language) vía `TEMPLATE_NAME_TAKEN` en el CRUD del
            dashboard; este preferir-FAKE cubre solo la ventana TOCTOU residual
            de un sync de Meta posterior). `BSUID_SEND_DISABLED`: el contacto no
            tiene número de teléfono y el envío a su identificador
            business-scoped (BSUID) no está habilitado para esta cuenta.
            `AUTH_TEMPLATE_REQUIRES_PHONE`: se intentó enviar una plantilla de
            categoría `AUTHENTICATION` a un contacto sin teléfono — Meta exige
            teléfono para plantillas de autenticación (no se entregan por
            BSUID). Además, el identificador de conversación puede resultar
            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: >-
            Estado que no admite envío: `TEMPLATE_NOT_APPROVED` (template con
            status PENDING/REJECTED en Meta para conversaciones reales; ni
            APPROVED ni FAKE para conversaciones tester).
            `TEMPLATE_FAKE_REQUIRES_TESTER_CONVERSATION` cuando el template es
            del módulo Tester (fake del operador o seed con `is_tester=TRUE`) y
            la conversación destino es real — solo se admite enviarlo a
            conversaciones con `is_tester=true`. Las conversaciones tester SÍ
            son aceptadas para ambos APPROVED y FAKE — replican el flujo real
            sin enviar a Meta, persisten con `is_tester=true` y emiten
            broadcasts al canal tester. `TEMPLATE_HEADER_TRANSCRIPTION_MISSING`
            cuando el header del template referencia un archivo de biblioteca
            cuya transcripción aún no está disponible (pendiente o fallida) —
            reintentar tras completarse la transcripción o reemplazar el
            archivo.
          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'
        '502':
          description: >-
            Meta rechazó el envío (SEND_FAILED). El mensaje quedó persistido en
            DB con status `failed`.
          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:
    SendTemplateBody:
      type: object
      properties:
        conversation:
          $ref: '#/components/schemas/ConversationRef'
        template_name:
          type: string
          minLength: 1
          maxLength: 512
          pattern: ^[a-zA-Z0-9_]+$
          example: promo_verano_2026
          description: >-
            Nombre del template aprobado por Meta. Debe matchear
            `^[a-zA-Z0-9_]+$` (alfanumérico + underscore); el servidor normaliza
            a lowercase per convención Meta. Único por business; si el mismo
            nombre existe en múltiples idiomas, la respuesta es
            `TEMPLATE_AMBIGUOUS` y debes especificar `language`.
        language:
          type: string
          minLength: 2
          maxLength: 10
          pattern: ^[a-zA-Z]{2,3}(_[a-zA-Z]{2,3})?$
          example: es_MX
          description: >-
            Idioma del template (opcional). Formato Meta: 2-3 letras + opcional
            `_` + 2-3 letras (`es`, `es_MX`, `pt_BR`). El servidor normaliza
            casing a `lower_UPPER`. Solo requerido si el mismo `template_name`
            existe en varios idiomas para el business.
        body_variables:
          type: array
          items:
            $ref: '#/components/schemas/TemplateVariable'
          description: Variables del body del template. Ordénalas por `index` (1-based).
        header:
          $ref: '#/components/schemas/TemplateHeader'
        button_parameters:
          type: array
          items:
            $ref: '#/components/schemas/TemplateButtonParameter'
          description: >-
            Parámetros para botones con placeholders dinámicos. Solo aplica a
            botones de tipo URL del template.
      required:
        - conversation
        - template_name
    SendTemplateResponse:
      type: object
      properties:
        message_uuid:
          type: string
          format: uuid
        wamid:
          type: string
          description: >-
            ID del mensaje asignado por Meta (wa-message-id). Garantizado
            non-empty en envíos reales (si Meta responde sin wamid, el endpoint
            devuelve 502 SEND_FAILED). En conversaciones tester retorna `""` por
            diseño — no hay envío a Meta.
      required:
        - message_uuid
        - wamid
    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.
    TemplateVariable:
      type: object
      properties:
        index:
          type: integer
          minimum: 1
          example: 1
          description: Posición 1-based. Corresponde a `{{1}}`, `{{2}}`, etc. del template.
        value:
          type: string
          example: Ana
      required:
        - index
        - value
      description: >-
        Valor a interpolar en un placeholder del body del template. `{{1}}` se
        llena con la variable de index=1, etc.
    TemplateHeader:
      oneOf:
        - $ref: '#/components/schemas/TemplateHeaderText'
        - $ref: '#/components/schemas/TemplateHeaderImage'
        - $ref: '#/components/schemas/TemplateHeaderVideo'
        - $ref: '#/components/schemas/TemplateHeaderDocument'
      discriminator:
        propertyName: type
        mapping:
          text:
            $ref: '#/components/schemas/TemplateHeaderText'
          image:
            $ref: '#/components/schemas/TemplateHeaderImage'
          video:
            $ref: '#/components/schemas/TemplateHeaderVideo'
          document:
            $ref: '#/components/schemas/TemplateHeaderDocument'
      description: >-
        Header del template. Para media el cliente NO envía URLs — pasa
        `file_uuid` de un archivo previamente subido vía `/files/request-upload`
        + `/files/confirm`. El servidor genera una signed URL fresca al enviar.
    TemplateButtonParameter:
      type: object
      properties:
        subType:
          type: string
          enum:
            - url
        index:
          type: integer
          minimum: 0
          example: 0
        text:
          type: string
          example: ver-promo
      required:
        - subType
        - index
        - text
      description: >-
        Parámetro para botón de tipo URL con placeholder dinámico. `text`
        reemplaza `{{1}}` en la URL del botón.
    TemplateHeaderText:
      type: object
      properties:
        type:
          type: string
          enum:
            - text
        text:
          type: string
          example: Promo verano
      required:
        - type
        - text
    TemplateHeaderImage:
      type: object
      properties:
        type:
          type: string
          enum:
            - image
        file_uuid:
          type: string
          format: uuid
      required:
        - type
        - file_uuid
    TemplateHeaderVideo:
      type: object
      properties:
        type:
          type: string
          enum:
            - video
        file_uuid:
          type: string
          format: uuid
      required:
        - type
        - file_uuid
    TemplateHeaderDocument:
      type: object
      properties:
        type:
          type: string
          enum:
            - document
        file_uuid:
          type: string
          format: uuid
        filename:
          type: string
          example: invoice.pdf
      required:
        - type
        - file_uuid
  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_...

````