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

# Export messages

> Export chat messages as an NDJSON stream for qualitative analysis with anonymized IDs

## Description

Exports chat messages as an NDJSON (Newline-Delimited JSON) stream for qualitative analysis. All person IDs are automatically anonymized using HMAC-SHA256.

<Warning>
  The response is an NDJSON stream (`application/x-ndjson`), not standard JSON. Each line is an independent JSON object.
</Warning>

## Authentication header

<ParamField header="X-API-Key" type="string" required>
  Your Mindo API Key. Two types are accepted:

  * **Global (cross-company):** `mindo_global_<key>` — access to all companies
  * **Company-scoped:** `mindo_xxxxxxxxxxxxxxxxxxxxxxxx` — limited to the associated company
</ParamField>

## Query parameters

<ParamField query="company_id" type="string" required>
  Company ID. Accepts a single integer or comma-separated list: `42` or `42,57`.
</ParamField>

<ParamField query="from" type="string" required>
  Start date/time (ISO-8601, inclusive). Example: `2026-04-01T00:00:00Z`.
</ParamField>

<ParamField query="to" type="string" required>
  End date/time (ISO-8601, exclusive). Must be greater than `from`. Maximum 31-day range.
</ParamField>

<ParamField query="channel" type="string" default="all">
  Platform filter: `whatsapp`, `instagram`, `messenger`, `all`.
</ParamField>

<ParamField query="agent_id" type="string">
  Filter by AI agent. A single integer or CSV list: `5` or `5,12`.
</ParamField>

<ParamField query="direction" type="string" default="all">
  Message direction: `incoming`, `outgoing`, `all`.
</ParamField>

<ParamField query="include" type="string" default="classifications">
  Extra fields to include (CSV): `classifications`, `extractions`, `tool_calls`, `trace_id`.
</ParamField>

<ParamField query="cursor" type="string">
  Pagination cursor obtained from `next_cursor` in the previous response.
</ParamField>

<ParamField query="limit" type="integer" default="5000">
  Maximum number of rows per call. Range: 1–10000.
</ParamField>

## Response

The response is an NDJSON stream. Each line is a JSON object with the following fields:

<ResponseField name="message_id" type="string">
  Anonymized message ID (`msg_<24 hex chars>`).
</ResponseField>

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

<ResponseField name="occurred_at" type="string">
  ISO-8601 timestamp of when the message was sent.
</ResponseField>

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

<ResponseField name="direction" type="string">
  `"inbound"` (from contact) or `"outbound"` (from system/agent/operator).
</ResponseField>

<ResponseField name="author" type="object">
  Who sent the message.

  <Expandable title="Author properties">
    <ResponseField name="type" type="string">
      Author type: `contact`, `agent`, `human_operator`, `system`.
    </ResponseField>

    <ResponseField name="id" type="string">
      Anonymized author ID (`ctc_`, `agt_`, `op_` depending on type). `null` for `system`.
    </ResponseField>

    <ResponseField name="display_name" type="string">
      Visible author name. Only present for `agent`.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="agent" type="object">
  AI agent data (only if the message was generated by an agent).

  <Expandable title="Agent properties">
    <ResponseField name="id" type="integer">
      Real AI agent ID (not anonymized).
    </ResponseField>

    <ResponseField name="name" type="string">
      Agent name.
    </ResponseField>

    <ResponseField name="version" type="string">
      Agent version.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="content" type="object">
  Message content.

  <Expandable title="Content properties">
    <ResponseField name="type" type="string">
      Type: `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">
      Text content of the message.
    </ResponseField>

    <ResponseField name="media_url" type="string">
      Multimedia file URL (if applicable).
    </ResponseField>

    <ResponseField name="media_mime" type="string">
      Media MIME type (e.g., `image/jpeg`).
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="context" type="object">
  Conversation context.

  <Expandable title="Context properties">
    <ResponseField name="reply_to_message_id" type="string">
      Anonymized ID of the quoted message (`msg_<hash>`).
    </ResponseField>

    <ResponseField name="thread_position" type="null">
      Reserved for future use.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="metadata" type="object">
  Export data.

  <Expandable title="Metadata properties">
    <ResponseField name="client_id" type="string">
      Anonymized company ID (`cmp_<hash>`).
    </ResponseField>

    <ResponseField name="company_id" type="integer">
      Real company ID.
    </ResponseField>

    <ResponseField name="exported_at" type="string">
      UTC timestamp of when this record was generated.
    </ResponseField>

    <ResponseField name="schema_version" type="string">
      Schema version (`"1.0"`).
    </ResponseField>
  </Expandable>
</ResponseField>

### Optional fields (`include` parameter)

These fields only appear if requested in the `include` parameter.

<ResponseField name="classifications" type="array">
  Classifications applied to the message (included by default).

  <Expandable title="Classification properties">
    <ResponseField name="label" type="string">
      Classifier name.
    </ResponseField>

    <ResponseField name="value" type="any">
      Classification result.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="extractions" type="array">
  Data extracted from the message by custom field extractors.

  <Expandable title="Extraction properties">
    <ResponseField name="key" type="string">
      Custom field name.
    </ResponseField>

    <ResponseField name="value" type="any">
      Extracted value.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="tool_calls" type="array">
  Tools executed by the AI agent during message generation.

  <Expandable title="Tool call properties">
    <ResponseField name="tool_name" type="string">
      Tool name.
    </ResponseField>

    <ResponseField name="success" type="boolean">
      Whether the execution was successful.
    </ResponseField>

    <ResponseField name="execution_time_ms" type="integer">
      Execution time in milliseconds.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="trace_id" type="string">
  Langfuse trace ID for debugging. Only has a value for outgoing messages generated by an AI agent.
</ResponseField>

### Pagination

Pagination is cursor-based. If there are more results, the last line of the stream is a sentinel:

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

Send the next request with `cursor=<next_cursor>` to get the next page. When there is no sentinel, all data has been returned.

### ID anonymization

| Prefix  | Represents               |
| ------- | ------------------------ |
| `msg_`  | Message ID               |
| `conv_` | Conversation ID          |
| `ctc_`  | Contact ID               |
| `agt_`  | Agent ID (in author)     |
| `op_`   | Human operator ID        |
| `cmp_`  | Company ID (in metadata) |

Anonymization is deterministic per company, irreversible, and consistent across exports.

<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"Next page: {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("Next page:", obj.next_cursor);
      continue;
    }
    console.log(obj.message_id, obj.content.text);
  }
  ```
</RequestExample>

<ResponseExample>
  ```json 200 - OK (NDJSON, one line per message) 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": "Hi, I want to check my order status",
      "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": "Intent", "value": "order_inquiry" }
    ]
  }
  ```

  ```json 400 - Invalid parameter theme={null}
  {
    "error": {
      "code": "INVALID_PARAMETER",
      "message": "from must be < to"
    }
  }
  ```

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

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

## Channel mapping

| `channel` parameter value | Included platforms                                      |
| ------------------------- | ------------------------------------------------------- |
| `whatsapp`                | META\_WHATSAPP, WHATSAPP\_EVOLUTION, MANYCHAT\_WHATSAPP |
| `instagram`               | INSTAGRAM, MANYCHAT\_INSTAGRAM                          |
| `messenger`               | MESSENGER                                               |
| `all`                     | All platforms                                           |

## Mid-stream errors

If an error occurs after the stream has already started, it is emitted as an additional NDJSON line:

```json theme={null}
{"_error": {"code": "ERROR_CODE", "message": "error description"}}
```

## Limits

| Restriction              | Value                  |
| ------------------------ | ---------------------- |
| Maximum date range       | 31 days                |
| Maximum rows per request | 10,000                 |
| Default rows per request | 5,000                  |
| Deleted messages         | Automatically excluded |

## Use cases

<AccordionGroup>
  <Accordion title="Agent response quality analysis">
    Export only outgoing messages from a specific agent with tool\_calls to evaluate which tools were used and how successfully:

    ```
    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="Training dataset for classifiers">
    Export incoming messages with their classifications to evaluate or retrain models:

    ```
    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="Cross-platform audit">
    Export all messages from multiple companies for comparative audit:

    ```
    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="Agent debugging with Langfuse">
    Export messages with `trace_id` to correlate with Langfuse traces:

    ```
    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>
