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

# Manage conversations with the API

> Read conversation details and history, update fields, assign work, and add internal notes through public API requests or CXP MCP tools.

# Manage conversations with the API

Use these operations to read or update an existing conversation, assign
ownership, or save an internal note. Each operation takes one request. The corresponding CXP
MCP tools use the same business rules with your connected user's permissions.

## Prerequisites

Obtain your API base URL, tenant ID, and a tenant API key with
`cxm:conversations.read` for reads and `cxm:conversations.write` for writes,
or a broader scope allowing the requested action. A write-only scope does not
grant read permission. Keep the key on your server.
Use an exact conversation ID and, for assignment, an active agent or team ID
from the same tenant. See [API overview](./api-overview) for authentication.

Send `Authorization: Bearer <tenant-api-key>`. Every path below starts with
`/api/v1/cxm/tenants/{tenantId}`. GET requests accept no body. Writes use
`Content-Type: application/json` and a complete JSON body within **32 KiB**.
Unknown fields and unsupported query parameters are rejected.

## Read conversation details

Send `GET /conversations/{conversationId}` with no query parameters:

```json theme={null}
{
  "document": {"id": "CX123", "status": "open", "priority": "high"},
  "freshness": {
    "source": "elasticsearch",
    "projectionUpdatedAt": "2026-09-07T12:00:00.000Z",
    "readAt": "2026-09-07T12:00:01.000Z"
  }
}
```

The document contains all available projected properties except its root
`messages` property. Its fields can evolve over time. Reads return only the exact requested tenant's data, even when conversation IDs are shared across tenants. This is the same read
used by `get_conversation`; MCP adds its existing context/result metadata.
Projection can lag recent writes. A missing projection returns **200** with
`document: null`; it does not establish whether the durable conversation exists.
`projectionUpdatedAt` is null when unavailable.

## Read conversation history

Send `GET /conversations/{conversationId}/history?limit=100`. The response is
`{"items":[...],"nextCursor":"..."}`. Each item has `type: "message"` or
`type: "event"` and the corresponding stored `record`.

History includes stored messages, internal notes, and recorded events in
oldest-first order, using the same implementation as `get_conversation_history`.
Messages include their actual author, visibility, role, body, and metadata;
events include their kind, title, detail, and metadata. Existing History
redaction and size limits apply, with `redactedPaths`, `truncatedFields`, and
`sourceByteSizes` recording transformations.

`limit` is an integer from **1 to 200**, default **100**. Pass the opaque
`nextCursor` unchanged as the URL-encoded `cursor` query parameter to continue
for the same tenant and conversation. Continue until `nextCursor` is null.
Only one page is read per request. Byte limits can produce fewer items than the
requested limit. Missing or empty history returns **200** with
`{"items":[],"nextCursor":null}`.

Unknown, repeated, array, and empty query parameters are rejected. Invalid
limits or cursors return **400**. Both read endpoints use
`Cache-Control: no-store`. API keys with read access can retrieve internal
history as well as customer-visible content.

## Update operational fields

Send `PATCH /conversations/{conversationId}`:

```json theme={null}
{
  "changes": {
    "status": "pending",
    "pendingOn": "customer",
    "pendingReason": "Awaiting the invoice number"
  }
}
```

| Field           | Allowed values                                                                      |
| --------------- | ----------------------------------------------------------------------------------- |
| `status`        | `open`, `pending`, `resolved`, `closed`                                             |
| `priority`      | `low`, `normal`, `high`, `urgent`                                                   |
| `pendingOn`     | `customer`, an exact configured Pending On value, or `null`; at most 255 characters |
| `pendingReason` | Non-blank text up to 2,000 characters, or `null`                                    |

Provide at least one field. Normal status transitions and required custom-field
checks apply. Open and Pending can transition to each other or Resolved;
Resolved can transition to Open, Pending, or Closed. Closed can only reopen to
Open. Simple mode also prevents reopening when the contact already has another
non-closed conversation.

Non-null Pending fields require the resulting status to be Pending. Omitted
Pending fields are preserved while Pending; leaving Pending clears them.
Subject, tags, custom fields, contact, organization, and category edits are not
part of this endpoint.

## Assign or clear ownership

Send `PUT /conversations/{conversationId}/assignment`:

```json theme={null}
{
  "assignee": {
    "type": "agent",
    "id": "agent_demo"
  }
}
```

Use `type: "team"` with an exact team ID to assign a team. Send
`{"assignee": null}` to clear assignment. Names are not accepted as selectors.
Assigning a team sets the conversation's team association; assigning an agent
preserves the current team association; clearing assignment also clears it.

Existing realtime ownership rules still apply. This operation does not take
over a conversation, accept a routing offer, or join a call. If CXP returns
`takeover_required`, use the normal CXP takeover workflow.

## Add an internal note

Send `POST /conversations/{conversationId}/internal-notes`:

```json theme={null}
{
  "body": "Customer requested a billing review."
}
```

The body must contain non-blank text with at most 20,000 characters, within the
32 KiB JSON limit. CXP fixes the note's internal visibility and attributes it to
`API <key name>`. You cannot supply a role, author, visibility, attachment, or
message ID. The note is never sent to the customer. Normal internal follower
notifications and Agent Assist behavior can still run.

## Results and execution behavior

Updates and assignments return **200** with a `conversation` containing its ID,
status, priority, Pending fields, assignee, and team association. Notes return
**201** with a `note` containing its ID, creation time, internal visibility, and
author label. API-key identifiers are omitted. Success does not mean notification
delivery has finished.

Merged and non-customer records cannot be changed. Closed conversations reject
assignment, notes, and metadata changes unless an update explicitly reopens them.
You can reopen and change other allowed metadata in one update, such as
`{"changes":{"status":"open","priority":"high"}}`. Assignment and notes require
the conversation to be reopened first. These restrictions apply to the current
conversation state when the operation executes.

Each invocation is independent. There is no preview/apply step, caller version
condition, idempotency key, request deduplication, or automatic retry. A later
valid update can replace a value changed since you last read it. A timeout can
follow a successful commit; repeating that request may repeat its effects,
including creating another identical note. Inspect the conversation or its
history before deciding whether another write is needed. Request/correlation
headers are for tracing only.

## Use the same operations through MCP

Reads use `get_conversation` and `get_conversation_history` with **Read CXP data**
and your current Conversation read permission. Tool inputs use the same exact
`tenantId` and `conversationId`; history also accepts `limit` and `cursor`.

[Connect CXP to ChatGPT or Codex](./connect-chatgpt-codex) with **Modify CXP data**.
Use `update_conversation`, `assign_conversation`, or
`add_conversation_internal_note`. Each tool requires your current Conversation
read access plus, respectively, update, assignment, or reply permission.

Tool inputs contain `tenantId`, `conversationId`, and the payload above.
`assign_conversation` additionally accepts `{"assignee":{"type":"me"}}` to
select your connected tenant user. API keys have no `me` identity. Notes retain
your actual authorship, even if someone else owns the conversation.

The tools execute directly and honor normal host approval controls. CXP does
not provide a separate confirmation page or independently attest to human
approval of each host call. Preparing a brief alone does not save a note.
