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

# Exportar mensajes

> Exporta mensajes de chat como stream NDJSON para análisis cualitativo con IDs anonimizados

## Descripción

Exporta mensajes de chat como un stream NDJSON (Newline-Delimited JSON) para análisis cualitativo. Todos los IDs de personas se anonimizan automáticamente con HMAC-SHA256.

<Warning>
  La respuesta es un stream NDJSON (`application/x-ndjson`), no JSON estándar. Cada línea es un objeto JSON independiente.
</Warning>

## Header de autenticación

<ParamField header="X-API-Key" type="string" required>
  Tu API Key de Mindo. Acepta dos tipos:

  * **Global (cross-company):** `mindo_global_<key>` — acceso a todas las empresas
  * **De empresa:** `mindo_xxxxxxxxxxxxxxxxxxxxxxxx` — acceso limitado a la empresa asociada
</ParamField>

## Query parameters

<ParamField query="company_id" type="string" required>
  ID de empresa. Acepta un entero o lista separada por comas: `42` o `42,57`.
</ParamField>

<ParamField query="from" type="string" required>
  Fecha/hora de inicio (ISO-8601, inclusive). Ejemplo: `2026-04-01T00:00:00Z`.
</ParamField>

<ParamField query="to" type="string" required>
  Fecha/hora de fin (ISO-8601, exclusivo). Debe ser mayor que `from`. Máximo 31 días de rango.
</ParamField>

<ParamField query="channel" type="string" default="all">
  Filtro de plataforma: `whatsapp`, `instagram`, `messenger`, `all`.
</ParamField>

<ParamField query="agent_id" type="string">
  Filtrar por agente IA. Un entero o lista CSV: `5` o `5,12`.
</ParamField>

<ParamField query="direction" type="string" default="all">
  Dirección del mensaje: `incoming`, `outgoing`, `all`.
</ParamField>

<ParamField query="include" type="string" default="classifications">
  Campos extras a incluir (CSV): `classifications`, `extractions`, `tool_calls`, `trace_id`.
</ParamField>

<ParamField query="cursor" type="string">
  Cursor de paginación obtenido de `next_cursor` en la respuesta anterior.
</ParamField>

<ParamField query="limit" type="integer" default="5000">
  Cantidad máxima de filas por llamada. Rango: 1–10000.
</ParamField>

## Respuesta

La respuesta es un stream NDJSON. Cada línea es un objeto JSON con los siguientes campos:

<ResponseField name="message_id" type="string">
  ID anonimizado del mensaje (`msg_<24 hex chars>`).
</ResponseField>

<ResponseField name="conversation_id" type="string">
  ID anonimizado del chat (`conv_<24 hex chars>`).
</ResponseField>

<ResponseField name="occurred_at" type="string">
  Timestamp ISO-8601 de cuándo se envió el mensaje.
</ResponseField>

<ResponseField name="channel" type="string">
  Plataforma: `META_WHATSAPP`, `WHATSAPP_EVOLUTION`, `INSTAGRAM`, `MESSENGER`, `MANYCHAT_WHATSAPP`, `MANYCHAT_INSTAGRAM`.
</ResponseField>

<ResponseField name="direction" type="string">
  `"inbound"` (del contacto) o `"outbound"` (del sistema/agente/operador).
</ResponseField>

<ResponseField name="author" type="object">
  Quién envió el mensaje.

  <Expandable title="Propiedades de author">
    <ResponseField name="type" type="string">
      Tipo de autor: `contact`, `agent`, `human_operator`, `system`.
    </ResponseField>

    <ResponseField name="id" type="string">
      ID anonimizado del autor (`ctc_`, `agt_`, `op_` según tipo). `null` para `system`.
    </ResponseField>

    <ResponseField name="display_name" type="string">
      Nombre visible del autor. Solo presente para `agent`.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="agent" type="object">
  Datos del agente IA (solo si el mensaje fue generado por un agente).

  <Expandable title="Propiedades de agent">
    <ResponseField name="id" type="integer">
      ID real del agente IA (no anonimizado).
    </ResponseField>

    <ResponseField name="name" type="string">
      Nombre del agente.
    </ResponseField>

    <ResponseField name="version" type="string">
      Versión del agente.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="content" type="object">
  Contenido del mensaje.

  <Expandable title="Propiedades de content">
    <ResponseField name="type" type="string">
      Tipo: `text`, `image`, `video`, `audio`, `file`, `sticker`, `location`, `contact`, `system`, `notification_template`, `carousel`, `story_mention`, `story_reply`, `ice_breaker_response`.
    </ResponseField>

    <ResponseField name="text" type="string">
      Contenido textual del mensaje.
    </ResponseField>

    <ResponseField name="media_url" type="string">
      URL del archivo multimedia (si aplica).
    </ResponseField>

    <ResponseField name="media_mime" type="string">
      MIME type del media (ej: `image/jpeg`).
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="context" type="object">
  Contexto de la conversación.

  <Expandable title="Propiedades de context">
    <ResponseField name="reply_to_message_id" type="string">
      ID anonimizado del mensaje citado (`msg_<hash>`).
    </ResponseField>

    <ResponseField name="thread_position" type="null">
      Reservado para uso futuro.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="metadata" type="object">
  Datos de exportación.

  <Expandable title="Propiedades de metadata">
    <ResponseField name="client_id" type="string">
      ID anonimizado de la empresa (`cmp_<hash>`).
    </ResponseField>

    <ResponseField name="company_id" type="integer">
      ID real de la empresa.
    </ResponseField>

    <ResponseField name="exported_at" type="string">
      Timestamp UTC de cuándo se generó este registro.
    </ResponseField>

    <ResponseField name="schema_version" type="string">
      Versión del schema (`"1.0"`).
    </ResponseField>
  </Expandable>
</ResponseField>

### Campos opcionales (parámetro `include`)

Estos campos solo aparecen si se solicitan en el parámetro `include`.

<ResponseField name="classifications" type="array">
  Clasificaciones aplicadas al mensaje (incluido por defecto).

  <Expandable title="Propiedades de classifications">
    <ResponseField name="label" type="string">
      Nombre del clasificador.
    </ResponseField>

    <ResponseField name="value" type="any">
      Resultado de la clasificación.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="extractions" type="array">
  Datos extraídos del mensaje por extractores de custom fields.

  <Expandable title="Propiedades de extractions">
    <ResponseField name="key" type="string">
      Nombre del custom field.
    </ResponseField>

    <ResponseField name="value" type="any">
      Valor extraído.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="tool_calls" type="array">
  Herramientas ejecutadas por el agente IA durante la generación del mensaje.

  <Expandable title="Propiedades de tool_calls">
    <ResponseField name="tool_name" type="string">
      Nombre de la herramienta.
    </ResponseField>

    <ResponseField name="success" type="boolean">
      Si la ejecución fue exitosa.
    </ResponseField>

    <ResponseField name="execution_time_ms" type="integer">
      Tiempo de ejecución en milisegundos.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="trace_id" type="string">
  ID de traza en Langfuse para debugging. Solo tiene valor en mensajes outgoing generados por un agente IA.
</ResponseField>

### Paginación

La paginación es cursor-based. Si hay más resultados, la última línea del stream es un sentinel:

```json theme={null}
{"_truncated": true, "next_cursor": "<cursor_string>"}
```

Envía la siguiente request con `cursor=<next_cursor>` para obtener la página siguiente. Cuando no hay sentinel, se agotaron todos los datos.

### Anonimización de IDs

| Prefijo | Representa                  |
| ------- | --------------------------- |
| `msg_`  | ID de mensaje               |
| `conv_` | ID de conversación          |
| `ctc_`  | ID de contacto              |
| `agt_`  | ID de agente (en author)    |
| `op_`   | ID de operador humano       |
| `cmp_`  | ID de empresa (en metadata) |

La anonimización es determinista por empresa, irreversible y consistente entre exportaciones.

<RequestExample>
  ```bash cURL theme={null}
  curl -s \
    -H "X-API-Key: mindo_xxxxxxxxxxxxxxxxxxxxxxxx" \
    "https://api.mindosoftware.com/api/v1/qualitative-export/messages?company_id=42&from=2026-04-01T00:00:00Z&to=2026-04-07T00:00:00Z"
  ```

  ```python Python theme={null}
  import requests
  import json

  response = requests.get(
      "https://api.mindosoftware.com/api/v1/qualitative-export/messages",
      headers={"X-API-Key": "mindo_xxxxxxxxxxxxxxxxxxxxxxxx"},
      params={
          "company_id": "42",
          "from": "2026-04-01T00:00:00Z",
          "to": "2026-04-07T00:00:00Z",
          "include": "classifications,extractions,tool_calls,trace_id",
      },
      stream=True,
  )

  for line in response.iter_lines(decode_unicode=True):
      if not line:
          continue
      obj = json.loads(line)
      if obj.get("_truncated"):
          print(f"Siguiente página: {obj['next_cursor']}")
          continue
      print(obj["message_id"], obj["content"]["text"])
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch(
    "https://api.mindosoftware.com/api/v1/qualitative-export/messages?" +
      new URLSearchParams({
        company_id: "42",
        from: "2026-04-01T00:00:00Z",
        to: "2026-04-07T00:00:00Z",
        include: "classifications,extractions,tool_calls,trace_id",
      }),
    {
      headers: { "X-API-Key": "mindo_xxxxxxxxxxxxxxxxxxxxxxxx" },
    }
  );

  const text = await response.text();
  const lines = text.split("\n").filter(Boolean);

  for (const line of lines) {
    const obj = JSON.parse(line);
    if (obj._truncated) {
      console.log("Siguiente página:", obj.next_cursor);
      continue;
    }
    console.log(obj.message_id, obj.content.text);
  }
  ```
</RequestExample>

<ResponseExample>
  ```json 200 - OK (NDJSON, una línea por mensaje) theme={null}
  {
    "message_id": "msg_a1b2c3d4e5f6a1b2c3d4e5f6",
    "conversation_id": "conv_f6e5d4c3b2a1f6e5d4c3b2a1",
    "occurred_at": "2026-04-05T12:30:45.123456",
    "channel": "META_WHATSAPP",
    "direction": "inbound",
    "author": {
      "type": "contact",
      "id": "ctc_a1b2c3d4e5f6a1b2c3d4e5f6",
      "display_name": null
    },
    "agent": null,
    "content": {
      "type": "text",
      "text": "Hola, quiero consultar el estado de mi pedido",
      "media_url": null,
      "media_mime": null
    },
    "context": {
      "reply_to_message_id": null,
      "thread_position": null
    },
    "metadata": {
      "client_id": "cmp_d4c3b2a1f6e5d4c3b2a1f6e5",
      "company_id": 42,
      "exported_at": "2026-05-05T18:30:00.000000Z",
      "schema_version": "1.0"
    },
    "classifications": [
      { "label": "Intención", "value": "consulta_pedido" }
    ]
  }
  ```

  ```json 400 - Parámetro inválido theme={null}
  {
    "error": {
      "code": "INVALID_PARAMETER",
      "message": "from must be < to"
    }
  }
  ```

  ```json 401 - No autenticado theme={null}
  {
    "error": {
      "code": "AUTH_REQUIRED",
      "message": "API Key required (X-API-Key header)"
    }
  }
  ```

  ```json 403 - Sin acceso theme={null}
  {
    "error": {
      "code": "COMPANY_ACCESS_DENIED",
      "message": "API key has no access to company_id=123"
    }
  }
  ```
</ResponseExample>

## Mapeo de canales

| Valor del parámetro `channel` | Plataformas incluidas                                   |
| ----------------------------- | ------------------------------------------------------- |
| `whatsapp`                    | META\_WHATSAPP, WHATSAPP\_EVOLUTION, MANYCHAT\_WHATSAPP |
| `instagram`                   | INSTAGRAM, MANYCHAT\_INSTAGRAM                          |
| `messenger`                   | MESSENGER                                               |
| `all`                         | Todas las plataformas                                   |

## Errores mid-stream

Si ocurre un error después de que el stream ya comenzó, se emite como una línea NDJSON adicional:

```json theme={null}
{"_error": {"code": "ERROR_CODE", "message": "descripción del error"}}
```

## Límites

| Restricción                  | Valor                     |
| ---------------------------- | ------------------------- |
| Rango máximo de fechas       | 31 días                   |
| Máximo de filas por request  | 10,000                    |
| Default de filas por request | 5,000                     |
| Mensajes eliminados          | Excluidos automáticamente |

## Casos de uso

<AccordionGroup>
  <Accordion title="Análisis de calidad de respuestas de agentes">
    Exportar solo mensajes outgoing de un agente específico con tool\_calls para evaluar qué herramientas usó y con qué éxito:

    ```
    GET /api/v1/qualitative-export/messages?company_id=42&from=2026-04-01T00:00:00Z&to=2026-04-07T00:00:00Z&agent_id=5&direction=outgoing&include=classifications,tool_calls,trace_id
    ```
  </Accordion>

  <Accordion title="Dataset de entrenamiento para clasificadores">
    Exportar mensajes incoming con sus clasificaciones para evaluar o reentrenar modelos:

    ```
    GET /api/v1/qualitative-export/messages?company_id=42&from=2026-04-01T00:00:00Z&to=2026-04-30T00:00:00Z&direction=incoming&include=classifications,extractions
    ```
  </Accordion>

  <Accordion title="Auditoría cross-platform">
    Exportar todos los mensajes de múltiples empresas para auditoría comparativa:

    ```
    GET /api/v1/qualitative-export/messages?company_id=42,57&from=2026-04-01T00:00:00Z&to=2026-04-07T00:00:00Z&include=classifications
    ```
  </Accordion>

  <Accordion title="Debugging de agentes con Langfuse">
    Exportar mensajes con `trace_id` para correlacionar con trazas en Langfuse:

    ```
    GET /api/v1/qualitative-export/messages?company_id=42&from=2026-04-05T10:00:00Z&to=2026-04-05T12:00:00Z&include=tool_calls,trace_id
    ```
  </Accordion>
</AccordionGroup>
