# AI Chat Export Normalizer (`z3n_m4nt1s/ai-chat-export-normalizer`) Actor

Turn ChatGPT and Claude conversations.json files or export ZIPs into clean, stable, receipt-bearing JSONL records.

- **URL**: https://apify.com/z3n\_m4nt1s/ai-chat-export-normalizer.md
- **Developed by:** [theCR8Fconclave SYST3MS](https://apify.com/z3n_m4nt1s) (community)
- **Categories:** AI, Developer tools, Automation
- **Stats:** 2 total users, 1 monthly users, 85.7% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$0.50 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## AI Chat Export Normalizer

Turn ChatGPT and Claude exports into clean, stable, receipt-bearing records for search, migration, analysis, reconstruction, and downstream data workflows.

### What it accepts

Provide exactly one source:

- `sourceUrl`: a retrievable HTTP(S) URL for `conversations.json` or an export ZIP
- `fileBase64` plus `fileName`: a small JSON or ZIP encoded as base64
- `conversations`: a small inline ChatGPT or Claude conversations array

Optional controls:

- `providerHint`: `auto` (default), `chatgpt`, or `claude`
- `privacyMode`: `metadata_only` (default) or `full_text`
- `maxInputBytes`: decoded/extracted safety limit; 25 MiB by default, 100 MiB maximum

Direct browser-style file upload is not part of this Actor input contract. Large private exports should be normalized locally instead of embedded in Actor input. A signed URL can work for a retrievable file, but its access and retention policy remain the buyer's responsibility.

### What it returns

The default Dataset and `NORMALIZED_JSONL` key contain these record types:

- `conversation`: title, timestamps, provider, participants, models, message IDs, stable ID, source hash, and duplicate flags
- `message`: conversation link, sequence, parent, role, participant, model, timestamps, normalized content blocks, and stable ID
- `duplicate_group`: matching conversation fingerprints and member IDs
- `source_manifest`: source type, safe file name, byte counts, SHA-256 hashes, provider, and processed counts
- `run_receipt`: status, privacy mode, stable-ID algorithm, counts, source hash, and run timestamp

`OUTPUT` stays compact. It contains the provider, privacy mode, counts, source manifest, run receipt, Dataset record count, and the `NORMALIZED_JSONL` key name without repeating every conversation.

Stable IDs use canonical JSON plus SHA-256 (`sha256/canonical-json/v1`). IDs do not change when the same export is rerun under `metadata_only` versus `full_text`.

### Privacy behavior

#### `metadata_only` — safer default

Message text is omitted. Each content block retains only:

- normalized block type
- block index
- character count
- SHA-256 hash

Conversation titles and other export metadata remain present.

#### `full_text` — explicit opt-in

Normalized content blocks include message text. The text is written to the run's Dataset and `NORMALIZED_JSONL` record.

The Actor does not print full conversations or credentials to logs. Outputs remain in the Actor run's storage and follow the retention/deletion settings of that Apify account and storage. This package does not claim end-to-end encryption or automatic deletion. Delete run storage manually when the material should no longer be retained.

Do not submit secrets, API keys, customer records, or exports you are not authorized to process.

### Provider coverage

#### ChatGPT

Supports the standard `conversations.json` mapping shape. All message-bearing mapping nodes are normalized, including branch relationships where present.

#### Claude

Supports the standard `conversations.json` shape containing `chat_messages`. Human senders normalize to the `user` role.

Attachments and provider-only objects are represented as normalized content-block type/hash/count records. This release does not extract attachment binaries.

### Local use

Install and test:

```bash
npm install
npm test
```

Run through the local Apify CLI:

```bash
apify validate-schema
apify run --purge --input-file examples/sample-input-chatgpt.json
```

Local output appears under:

- `storage/key_value_stores/default/OUTPUT.json`
- `storage/key_value_stores/default/NORMALIZED_JSONL.jsonl`
- `storage/datasets/default/*.json`

A plain Node run is also supported with `APIFY_INPUT_JSON` and `APIFY_LOCAL_STORAGE_DIR`.

### Failure behavior

The Actor exits non-zero with a concise error when:

- no source or more than one source is supplied
- provider hint conflicts with the detected export
- JSON is malformed
- the export is neither ChatGPT nor Claude
- a ZIP has no `conversations.json` or contains more than one
- input exceeds the configured byte limit
- a URL uses a private, loopback, or non-HTTP(S) address

### Extension contract

Future provider adapters should preserve the current record types and stable-ID rules. Add provider detection and a parser that emits the same intermediate conversation/message shape; do not fork the Dataset contract for each provider.

Synthetic-only examples and fixtures are included. No real user, account, client, or company export content is packaged.

# Actor input Schema

## `sourceUrl` (type: `string`):

Publicly retrievable or signed HTTP(S) URL for conversations.json or an export ZIP. Private and loopback hosts are blocked.

## `fileBase64` (type: `string`):

Base64-encoded conversations.json or export ZIP for small files. Large private archives should be run locally instead of embedded in Actor input.

## `fileName` (type: `string`):

Required with fileBase64. Use conversations.json or a .zip name.

## `conversations` (type: `array`):

Optional small ChatGPT or Claude conversations array. Intended for tests and small controlled inputs.

## `providerHint` (type: `string`):

Auto-detect by default. A conflicting hint fails clearly.

## `privacyMode` (type: `string`):

metadata\_only omits message text while retaining hashes and counts. full\_text includes normalized message text in run storage.

## `maxInputBytes` (type: `integer`):

Safety limit for the fetched, decoded, or extracted conversations.json file.

## Actor input object example

```json
{
  "fileName": "conversations.json",
  "conversations": [
    {
      "id": "apify-qa-synthetic-001",
      "title": "Synthetic Export Normalization Example",
      "create_time": 1760100000,
      "update_time": 1760100060,
      "current_node": "apify-qa-assistant-001",
      "mapping": {
        "apify-qa-root-001": {
          "id": "apify-qa-root-001",
          "parent": null,
          "children": [
            "apify-qa-user-001"
          ],
          "message": null
        },
        "apify-qa-user-001": {
          "id": "apify-qa-user-001",
          "parent": "apify-qa-root-001",
          "children": [
            "apify-qa-assistant-001"
          ],
          "message": {
            "id": "apify-qa-message-user-001",
            "author": {
              "role": "user",
              "name": null,
              "metadata": {}
            },
            "create_time": 1760100001,
            "content": {
              "content_type": "text",
              "parts": [
                "Normalize this synthetic conversation export."
              ]
            },
            "metadata": {}
          }
        },
        "apify-qa-assistant-001": {
          "id": "apify-qa-assistant-001",
          "parent": "apify-qa-user-001",
          "children": [],
          "message": {
            "id": "apify-qa-message-assistant-001",
            "author": {
              "role": "assistant",
              "name": null,
              "metadata": {}
            },
            "create_time": 1760100002,
            "content": {
              "content_type": "text",
              "parts": [
                "Synthetic export accepted for automated quality assurance."
              ]
            },
            "metadata": {
              "model_slug": "synthetic-qa-example"
            }
          }
        }
      }
    }
  ],
  "providerHint": "auto",
  "privacyMode": "metadata_only",
  "maxInputBytes": 26214400
}
```

# Actor output Schema

## `results` (type: `string`):

Conversation, message, duplicate-group, source-manifest, and run-receipt records in the default Dataset.

## `summary` (type: `string`):

Compact run status, counts, source manifest, and run receipt from the default key-value store.

## `normalizedJsonl` (type: `string`):

All normalized records as newline-delimited JSON for downstream ingestion.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "conversations": [
        {
            "id": "apify-qa-synthetic-001",
            "title": "Synthetic Export Normalization Example",
            "create_time": 1760100000,
            "update_time": 1760100060,
            "current_node": "apify-qa-assistant-001",
            "mapping": {
                "apify-qa-root-001": {
                    "id": "apify-qa-root-001",
                    "parent": null,
                    "children": [
                        "apify-qa-user-001"
                    ],
                    "message": null
                },
                "apify-qa-user-001": {
                    "id": "apify-qa-user-001",
                    "parent": "apify-qa-root-001",
                    "children": [
                        "apify-qa-assistant-001"
                    ],
                    "message": {
                        "id": "apify-qa-message-user-001",
                        "author": {
                            "role": "user",
                            "name": null,
                            "metadata": {}
                        },
                        "create_time": 1760100001,
                        "content": {
                            "content_type": "text",
                            "parts": [
                                "Normalize this synthetic conversation export."
                            ]
                        },
                        "metadata": {}
                    }
                },
                "apify-qa-assistant-001": {
                    "id": "apify-qa-assistant-001",
                    "parent": "apify-qa-user-001",
                    "children": [],
                    "message": {
                        "id": "apify-qa-message-assistant-001",
                        "author": {
                            "role": "assistant",
                            "name": null,
                            "metadata": {}
                        },
                        "create_time": 1760100002,
                        "content": {
                            "content_type": "text",
                            "parts": [
                                "Synthetic export accepted for automated quality assurance."
                            ]
                        },
                        "metadata": {
                            "model_slug": "synthetic-qa-example"
                        }
                    }
                }
            }
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("z3n_m4nt1s/ai-chat-export-normalizer").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "conversations": [{
            "id": "apify-qa-synthetic-001",
            "title": "Synthetic Export Normalization Example",
            "create_time": 1760100000,
            "update_time": 1760100060,
            "current_node": "apify-qa-assistant-001",
            "mapping": {
                "apify-qa-root-001": {
                    "id": "apify-qa-root-001",
                    "parent": None,
                    "children": ["apify-qa-user-001"],
                    "message": None,
                },
                "apify-qa-user-001": {
                    "id": "apify-qa-user-001",
                    "parent": "apify-qa-root-001",
                    "children": ["apify-qa-assistant-001"],
                    "message": {
                        "id": "apify-qa-message-user-001",
                        "author": {
                            "role": "user",
                            "name": None,
                            "metadata": {},
                        },
                        "create_time": 1760100001,
                        "content": {
                            "content_type": "text",
                            "parts": ["Normalize this synthetic conversation export."],
                        },
                        "metadata": {},
                    },
                },
                "apify-qa-assistant-001": {
                    "id": "apify-qa-assistant-001",
                    "parent": "apify-qa-user-001",
                    "children": [],
                    "message": {
                        "id": "apify-qa-message-assistant-001",
                        "author": {
                            "role": "assistant",
                            "name": None,
                            "metadata": {},
                        },
                        "create_time": 1760100002,
                        "content": {
                            "content_type": "text",
                            "parts": ["Synthetic export accepted for automated quality assurance."],
                        },
                        "metadata": { "model_slug": "synthetic-qa-example" },
                    },
                },
            },
        }] }

# Run the Actor and wait for it to finish
run = client.actor("z3n_m4nt1s/ai-chat-export-normalizer").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "conversations": [
    {
      "id": "apify-qa-synthetic-001",
      "title": "Synthetic Export Normalization Example",
      "create_time": 1760100000,
      "update_time": 1760100060,
      "current_node": "apify-qa-assistant-001",
      "mapping": {
        "apify-qa-root-001": {
          "id": "apify-qa-root-001",
          "parent": null,
          "children": [
            "apify-qa-user-001"
          ],
          "message": null
        },
        "apify-qa-user-001": {
          "id": "apify-qa-user-001",
          "parent": "apify-qa-root-001",
          "children": [
            "apify-qa-assistant-001"
          ],
          "message": {
            "id": "apify-qa-message-user-001",
            "author": {
              "role": "user",
              "name": null,
              "metadata": {}
            },
            "create_time": 1760100001,
            "content": {
              "content_type": "text",
              "parts": [
                "Normalize this synthetic conversation export."
              ]
            },
            "metadata": {}
          }
        },
        "apify-qa-assistant-001": {
          "id": "apify-qa-assistant-001",
          "parent": "apify-qa-user-001",
          "children": [],
          "message": {
            "id": "apify-qa-message-assistant-001",
            "author": {
              "role": "assistant",
              "name": null,
              "metadata": {}
            },
            "create_time": 1760100002,
            "content": {
              "content_type": "text",
              "parts": [
                "Synthetic export accepted for automated quality assurance."
              ]
            },
            "metadata": {
              "model_slug": "synthetic-qa-example"
            }
          }
        }
      }
    }
  ]
}' |
apify call z3n_m4nt1s/ai-chat-export-normalizer --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,z3n_m4nt1s/ai-chat-export-normalizer"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/xbiUVnR4hqQZdfqvh/builds/mhf50IF6RyKhlF1g3/openapi.json
