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

# Sincronizar conversaciones de WhatsApp

> Descarga el historial y las transcripciones a tus propios sistemas, de forma incremental

Los dos endpoints de WhatsApp son de **solo lectura**: permiten listar conversaciones con sus
transcripciones y entrar al historial completo de una conversación — para reportes, revisión
de calidad o para guardar una copia en tu CRM.

* **List WhatsApp conversations** — `GET /api/v2/public/whatsapp/conversations`
* **List messages in a WhatsApp conversation** — `GET /api/v2/public/whatsapp/conversations/{session_id}/messages`

Las páginas de cada endpoint documentan todos los parámetros y campos; esta guía trata de
cómo usarlos bien.

## Conceptos

* Una **conversación** (una *sesión*) es una ventana de conversación de WhatsApp acotada con un
  único número de cliente. Un cliente acumula varias conversaciones con el tiempo, a medida que
  las ventanas se abren y se cierran.
* Cada conversación pertenece a un **cliente** (cuando el número coincide con un contacto) y
  contiene una lista ordenada de **mensajes**.
* Las conversaciones tienen un **estado** (`new`, `ai_handling`, `human_handling`, `resolved`,
  `expired`), una **dirección** (`inbound` / `outbound`), una etiqueta de **disposición**
  opcional y un **sentimiento** opcional.

### Quién envió qué

Tres campos responden a tres preguntas distintas; no los confundas:

| Campo               | Nivel        | Responde                                                                                                              |
| ------------------- | ------------ | --------------------------------------------------------------------------------------------------------------------- |
| `messages[].sender` | mensaje      | **Quién envió este mensaje**: el agente de IA concreto, la persona del equipo o el cliente.                           |
| `participants`      | conversación | **Quién respondió en esta conversación**: cada agente de IA y persona que envió al menos un mensaje, con conteos.     |
| `assigned_users`    | cliente      | **Quién es dueño de este cliente**: su(s) asignado(s) en los pipelines de Salesflow. No dice nada de quién respondió. |

Una misma conversación suele mezclar varias personas y uno o más agentes de IA (un bot abre,
una persona toma el control, otra cierra), y por eso `participants` existe aparte de
`assigned_users`.

`participants` siempre está completo: cubre toda la conversación aunque pidas
`include_messages=false` o la transcripción venga recortada. Solo aparecen remitentes
identificados; los mensajes sincronizados desde la app de WhatsApp Business no tienen
remitente atribuible y no aportan ninguna entrada.

## Solicitudes habituales

<Tabs>
  <Tab title="Solo metadatos">
    La forma más rápida de ver qué hay. `participants` viene completo igualmente.

    ```bash theme={null}
    curl -H "Authorization: Bearer pk_xxx" \
      "https://api.dialtu.com/api/v2/public/whatsapp/conversations?limit=20&include_messages=false"
    ```
  </Tab>

  <Tab title="Transcripciones completas, más recientes primero">
    ```bash theme={null}
    curl -H "Authorization: Bearer pk_xxx" \
      "https://api.dialtu.com/api/v2/public/whatsapp/conversations?limit=10&sort_by=-created_at"
    ```
  </Tab>

  <Tab title="Filtrar por cliente y disposición">
    Repite `disposition_tag_ids` para un filtro OR; añade `include_uncategorized=true` para
    incluir también las conversaciones sin disposición.

    ```bash theme={null}
    curl -H "Authorization: Bearer pk_xxx" \
      "https://api.dialtu.com/api/v2/public/whatsapp/conversations?client_id=2&disposition_tag_ids=1&disposition_tag_ids=2&include_uncategorized=true"
    ```
  </Tab>

  <Tab title="Mensajes por persona del equipo">
    Como `participants` viene sin necesidad de transcripciones, el reporte por agente y por
    persona es barato:

    ```bash theme={null}
    curl -H "Authorization: Bearer pk_xxx" \
      "https://api.dialtu.com/api/v2/public/whatsapp/conversations?limit=50&include_messages=false&created_after=2026-06-01T00:00:00Z" \
    | jq '[.data[].participants.users[]]
          | group_by(.id)
          | map({ id: .[0].id, name: .[0].name, messages: (map(.message_count) | add) })
          | sort_by(-.messages)'
    ```
  </Tab>
</Tabs>

## Sincronización incremental

No vuelvas a descargar todo en cada ejecución:

<Steps>
  <Step title="Guarda el updated_at más reciente que hayas visto">
    Cada conversación trae `updated_at`, que avanza cuando llega un mensaje o cambian el
    estado, la disposición o el resumen.
  </Step>

  <Step title="Pide solo lo que cambió">
    En la siguiente ejecución envía esa marca como `updated_after` y ordena con
    `sort_by=updated_at` para un orden predecible:

    ```bash theme={null}
    curl -H "Authorization: Bearer pk_xxx" \
      "https://api.dialtu.com/api/v2/public/whatsapp/conversations?updated_after=2026-06-30T00:00:00Z&sort_by=updated_at"
    ```
  </Step>

  <Step title="Recorre las páginas">
    Mantén los mismos filtros y aumenta `offset` en `limit` hasta leer `count` elementos.
  </Step>
</Steps>

## Conversaciones largas

Cada conversación incluye como máximo `max_messages_per_session` mensajes (500 por defecto,
los más antiguos primero). Una transcripción más larga se recorta y se marca con
`has_more_messages: true`: pide el resto con el endpoint de mensajes, paginando con
`limit` / `offset`:

```bash theme={null}
curl -H "Authorization: Bearer pk_xxx" \
  "https://api.dialtu.com/api/v2/public/whatsapp/conversations/60/messages?limit=100&offset=0"
```

El endpoint de mensajes también devuelve el `participants` completo de la conversación, así
que nunca necesitas una segunda llamada al listado solo para eso.

## Conversación de ejemplo

```json theme={null}
{
  "session_id": 61,
  "status": "ai_handling",
  "direction": "inbound",
  "customer_phone_number": "+573013449768",
  "client": { "id": 2, "name": "Christian Gallo", "first_name": "Christian", "last_name": "Gallo", "phone": "3013449768", "email": null, "company": null },
  "disposition": null,
  "sentiment": null,
  "summary": "",
  "message_count": 2,
  "has_more_messages": false,
  "created_at": "2026-06-30T15:39:33.437Z",
  "updated_at": "2026-06-30T15:39:39.080Z",
  "last_message_at": "2026-06-30T15:39:51.309Z",
  "messages": [
    {
      "id": 561, "sender_type": "customer",
      "sender": { "type": "client", "id": 2, "name": "Christian Gallo", "email": null },
      "message_type": "text",
      "content": "Hola, ¿qué horarios hay disponibles?",
      "timestamp": "2026-06-30T15:39:33.482Z",
      "media": null, "reply_to_id": null
    },
    {
      "id": 562, "sender_type": "ai",
      "sender": { "type": "ai_agent", "id": 7, "name": "Sofia", "email": null },
      "message_type": "text",
      "content": "¡Hola! Tenemos disponibilidad mañana a las 10:00.",
      "timestamp": "2026-06-30T15:39:39.080Z",
      "media": null, "reply_to_id": 561
    }
  ],
  "assigned_users": [ { "id": 42, "name": "Ana Perez", "email": "ana@acme.com" } ],
  "participants": {
    "ai_agents": [ { "id": 7, "name": "Sofia", "message_count": 1, "first_message_at": "2026-06-30T15:39:39.080Z", "last_message_at": "2026-06-30T15:39:39.080Z" } ],
    "users": []
  }
}
```

## Notas y límites

* **Solo lectura.** Estos endpoints nunca modifican conversaciones.
* **Los archivos multimedia no se descargan**: solo se devuelven `kind`, `mime`, `filename` y
  `caption`.
* Los campos internos (modelo, tokens, costo, claves de almacenamiento) nunca se exponen.
* **Cambios aditivos.** Pueden añadirse campos nuevos (`sender` y `participants` llegaron así,
  junto a un `sender_type` sin cambios). Ignora los campos que no reconozcas.
* Hoy no se aplica límite de solicitudes; pagina con `limit` ≤ 50 en lugar de pedir cargas
  máximas en bucle.
