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

# Import a historical conversation

> Import a historical conversation and its messages with preserved timestamps and explicit contact references.

# Import a historical conversation

Import one historical conversation and its prepared transcript:

```http theme={null}
POST /api/v1/cxm/tenants/{tenantId}/conversations/import
```

Use a tenant API key with `cxm:conversations.write` or a broader scope. Import
stores a new conversation and all supplied messages together, then waits for
the search-indexing attempt before responding. It does not send messages,
start an Assistant, route work or notify agents during import. For new customer
intake, use [Create a conversation](https://docs.cxp.crescendo.ai/developers/create-conversation.md).

## Prepare the dataset

1. Transform source records into the fields below. Remove unsupported records,
   attachments and metadata, and convert voice content into plain-text transcripts.
2. [Create contacts](https://docs.cxp.crescendo.ai/developers/create-contact.md) and, if needed,
   [create organizations](https://docs.cxp.crescendo.ai/developers/create-organization.md). Save their returned IDs.
3. Import each conversation with an existing contact ID, or `anonymous` when
   you intentionally do not associate a named contact. An optional organization
   must also already exist in the tenant.
4. Save each returned conversation ID and any warnings. Keep a source-to-CXP ID
   mapping to investigate partial runs and uncertain outcomes.

Imports create records only. They cannot append to, merge with or replace an
existing conversation. Each request is limited to 1 MiB and 1,000 messages;
prune oversized transcripts before sending them. There is no continuation upload.

## Send a conversation

```bash theme={null}
curl --request POST \
  --url "https://platform.crescendo.ai/api/v1/cxm/tenants/tenant_demo/conversations/import" \
  --header "Authorization: Bearer $CXP_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "contact": {"contactId": "external-contact-123"},
    "organization": {"organizationId": "external-org-123"},
    "conversation": {
      "id": "external-conversation-456",
      "subject": "Replacement received",
      "status": "closed",
      "created_at": "2026-10-18T10:00:00Z",
      "closed_at": "2026-10-20T14:00:00Z",
      "channel": "email",
      "tags": ["historical-import"],
      "messages": [
        {
          "channel": "email",
          "role": "user",
          "content": "The replacement arrived. Thank you.",
          "created_at": "2026-10-20T12:00:00Z",
          "author_id": "external-contact-123",
          "author_name": "Jane Doe"
        },
        {
          "channel": "email",
          "role": "agent",
          "content": "Glad to hear it arrived.",
          "created_at": "2026-10-20T13:00:00Z",
          "author_id": "former-agent-7",
          "author_name": "Sam"
        }
      ]
    }
  }'
```

`contact` accepts only `contactId`; `organization` accepts only
`organizationId`. Import never updates either record. When you omit
`organization`, the new conversation inherits the contact's current
organization, or no organization for `anonymous`.

## Set conversation fields

| Field | Behavior |
| - | - |
| `id` | Optional external ID, up to 255 characters before normalization. CXP removes every ASCII colon (`:`). Supplied IDs cannot contain whitespace, control characters, `/`, `\`, `%`, `?` or `#`. After colon removal, the ID must be nonempty, cannot equal `.` or `..`, and cannot use uppercase `CX` or `AA` followed only by digits, including zero/leading-zero forms. Omit it to generate a native `CX` ID. |
| `subject` | Optional, up to 255 characters. Default is `Message from` followed by contact email, phone, display name or `Anonymous`, in that order. |
| `status` | `open`, `pending`, `resolved` or `closed`; default `closed`. Pending reason/time metadata is not accepted. |
| `created_at` | Required historical timestamp. |
| `updated_at` | Optional; defaults to the latest creation, lifecycle or message time. An explicit value must be at least all those times. |
| `resolved_at`, `closed_at` | Optional known historical times. Omitted values remain unknown, even when status is Resolved or Closed. |
| `channel` | Optional historical label from the supported channels below. Omission leaves it unset. |
| `assignee` | Optional `{type: "agent" or "team", id}` or `{type, name}`. Exactly one ID/name must resolve to an eligible current target. Omission leaves the conversation unassigned. |
| `team` | Optional `{teamId}` or `{name}`, exactly one. Must resolve to an eligible current team. If omitted, uses a team assignee's ID, otherwise remains unset. An explicit team must agree with a team assignee. |
| `priority` | `low`, `normal`, `high` or `urgent`; default `normal`. |
| `tags` | At most 50 strings, each up to 100 characters; case-insensitive deduplication. |
| `customFields` | Values for configured conversation custom fields, using the same validation as Create Conversation. |
| `messages` | Required array, which may be empty; maximum 1,000 entries. |

All timestamps must be valid RFC 3339 strings with an explicit timezone and
cannot be in the future. Message and lifecycle times cannot precede conversation
creation. CXP retains millisecond precision in UTC. It does not invent missing
lifecycle events or require a universal order between resolved and closed times,
which can represent different close/reopen cycles.

For example, `external:123` is stored and returned as `external123`. Use the
returned ID for subsequent requests and your source-to-CXP mapping. IDs that
differ only by colons identify the same conversation and return the ordinary
`ConversationIdConflict` error if it already exists. `:`, `:.` and `CX:1` are
invalid because their normalized values are empty, path-unsafe or reserved.
This normalization applies only to imported conversation IDs; contact and
organization IDs are unchanged.

Agent Assist child conversations require native `CX` parent IDs. Omit `id` if
that later capability is needed.

## Map messages

| Field | Behavior |
| - | - |
| `content` | Required nonblank plain text, maximum 100,000 characters. Content is preserved after validation. |
| `created_at` | Required historical timestamp, no earlier than conversation creation. |
| `role` | Required: `user`, `agent`, `assistant` or `system`. |
| `channel` | Optional, default `chat` independently of the parent conversation's channel. |
| `media` | Optional: `chat`, `email` or `voice`. Defaults to `email` for email channels, `voice` for voice/phone channels, and `chat` otherwise. |
| `author_id`, `author_name` | Optional historical attribution, each up to 255 characters. Authors do not need to exist as current CXP users. |
| `visibility` | `public` or `internal`; default `public`. Internal messages are hidden from customers, but remain available to authorized history readers and AI processing. |

Supported channel values are `chat`, `voice`, `email`, `sms`, `web_chat`,
`phone_call`, `web_audio`, `social`, `whatsapp`, `in_app`, `api` and `unknown`.
The voice media default applies to `voice`, `phone_call` and `web_audio`.

Every array entry is retained, including identical messages. Stored history
orders messages by time, preserving input order when times match. Search can
order equal-time messages differently. Message IDs are generated by CXP;
source message IDs, arbitrary metadata, attachments, recordings and provider
threads are not accepted. Voice messages are transcripts without call playback.

## Read the result and warnings

A successful import returns `201 Created`:

```json theme={null}
{
  "conversation": {
    "conversationId": "external-conversation-456",
    "result": "created",
    "contactId": "external-contact-123",
    "status": "closed",
    "messageCount": 2
  }
}
```

The response waits for the indexing attempt. If storage succeeds but indexing
fails, the conversation remains created and the response is still `201`, with:

```json theme={null}
{
  "warnings": [
    {
      "code": "IndexingFailed",
      "message": "Conversation was created, but search indexing failed"
    }
  ]
}
```

`IndexingConflict` also means creation succeeded, but an existing search
projection prevented this indexing attempt. Record either warning and ask your
CXP administrator or support contact to repair indexing. Search and public
conversation-detail reads can remain missing or stale until repair. Do not
retry the entire import solely to repair indexing.

| HTTP status | Code | Meaning |
| - | - | - |
| 400 | `BadRequest`, `InvalidCustomField` | Invalid field, reserved ID, timestamp, selector or custom value. |
| 404 | `ContactNotFound`, `OrganizationNotFound`, `AssigneeNotFound`, `TeamNotFound` | A referenced tenant resource is missing or ineligible. |
| 409 | `ConversationIdConflict` | Normalized ID already exists; `existingConversationId` contains that ID. |
| 409 | `ContactReadOnly` | The selected contact is a protected public-social contact. |
| 409 | `AssigneeAmbiguous`, `TeamAmbiguous` | Use an ID to resolve an ambiguous name. |
| 413 | `PayloadTooLarge` | Request exceeds the body limit. |
| 503 | `ServiceUnavailable` | A required dependency is unavailable; a known disabled indexer rejects before creation. |
| 502, 504 | `BadGateway`, `GatewayTimeout` | Outcome may be unknown, including after a successful commit. |

Authentication, rate limits and retry conventions are in the
[API overview](https://docs.cxp.crescendo.ai/developers/api-overview.md). There is no idempotency or replay guarantee.
A duplicate-ID error does not prove an earlier payload matched; a retry without
a preserved ID can create another conversation.

## Review imported history

The full supplied transcript and author metadata are stored. Search normally
includes the most recent 500 messages and omits some author/media fields.
Public history applies redaction and size limits, including 64 KiB per string
and 256 KiB per record/source value, so it is not a lossless export verifier.
Customer chat can display the current contact name instead of a historical
author-name snapshot.

Imported records follow ordinary authorization, retention and deletion rules.
Later activity can reuse Open, Pending or Resolved conversations and trigger
normal processing. Closed conversations can be explicitly reopened. Historical
channel labels do not create provider delivery connections.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.