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

# Ingest Export

> Upload and process an LLM conversation data export.

## `POST /api/ingest-export`

Ingests a conversation data export (JSON) for a given connection. The export file is normalized into individual conversations with a consistent `{ role, content, timestamp }` message format and stored in the Verso database.

Currently supports ChatGPT export format (DAG → linear message list normalization).

## Request

**Headers**

| Header | Value |
| - | - |
| `Authorization` | `Bearer YOUR_APP_SECRET` |
| `Content-Type` | `application/json` |

**Body**

```json theme={null}
{
  "appId": "app_yourapp",
  "connectionId": "uuid",
  "conversations": [
    {
      "title": "Conversation title",
      "create_time": 1700000000,
      "update_time": 1700001000,
      "mapping": { "..." : "..." },
      "conversation_id": "chatgpt-uuid"
    }
  ]
}
```

| Field | Type | Required | Description |
| - | - | - | - |
| `appId` | `string` | Yes | Your app identifier |
| `connectionId` | `string` | Yes | The connection to associate data with |
| `conversations` | `array` | Yes | Array of raw conversation objects from the export |

## Processing

The endpoint performs a **diff/upsert** for each conversation:

1. **Normalize** — raw export data is converted to `NormalizedConversation` (linear message list)
2. **Check existing** — looks up by `(connection_id, external_id)`
3. **Insert** — if no existing record
4. **Update** — if existing record has an older `update_time`
5. **Skip** — if existing record is the same or newer

After processing, the connection's `last_ok_at` is updated. If any conversations were inserted or updated, a `data.imported` webhook is enqueued.

## Response

```json theme={null}
{
  "ok": true,
  "inserted": 42,
  "updated": 3,
  "unchanged": 0,
  "total": 45
}
```

| Field | Type | Description |
| - | - | - |
| `ok` | `boolean` | Always `true` on success |
| `inserted` | `number` | New conversations inserted |
| `updated` | `number` | Existing conversations updated (newer version) |
| `unchanged` | `number` | Conversations skipped (same or older version) |
| `total` | `number` | Total conversations in the export |

## Errors

| Status | Error | Cause |
| - | - | - |
| 400 | `Missing appId` | `appId` field not provided |
| 400 | `Missing connectionId` | `connectionId` field not provided |
| 400 | `Invalid JSON body` | Request body is not valid JSON |
| 400 | `[normalization error]` | Export data could not be normalized |
| 401 | `Missing or invalid Authorization header` | Missing `Bearer` prefix |
| 401 | `Invalid API key` | Wrong or missing app secret |
| 403 | `Connection does not belong to this app` | Connection exists but belongs to a different app |
| 404 | `App not found` | App ID doesn't exist |
| 404 | `Connection not found` | Connection ID doesn't exist |
| 405 | `Method not allowed` | Must use `POST` |
| 409 | `Connection is revoked` | Connection was deleted |
