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

# Create a conversation

> Create or continue a CXP conversation from a form or backend automation.

# Create a conversation

Use this endpoint when a customer submits a form or when a trusted backend
needs to add a customer message to CXP:

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

`intakeChannel`, `contact.email`, and `message.text` are required. A successful response is
`201 Created` when CXP creates a conversation or `200 OK` when Simple mode
appends the message to an existing conversation.

## Send the minimal request

```bash theme={null}
curl --request POST \
  --url "https://platform.crescendo.ai/api/v1/cxm/tenants/tenant_demo/conversations" \
  --header "Authorization: Bearer $CXP_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "intakeChannel": "support@example.com",
    "contact": {
      "email": "jane@example.com"
    },
    "message": {
      "text": "Please contact me about your product."
    }
  }'
```

`intakeChannel` must be an active tenant service-email address with an active
AI Assistant. CXP uses the channel and contact identity with the same canonical
conversation-resolution flow used for inbound email. If no matching contact
exists, CXP creates one. If a contact exists, CXP adds the message to the
newest non-Closed conversation in Simple mode or creates a new conversation in
Advanced mode.

If you omit `conversation.subject` and the resolved conversation has no
subject, CXP sets `Message from {contact.email}`. In the minimal request above,
the subject is `Message from jane@example.com`. An existing conversation
subject is never replaced by this fallback.

## Map a website contact form

This example maps name, phone, email, company, subject, product, and question
fields. `intakeChannel` is an active service email configured in your tenant.

```json theme={null}
{
  "intakeChannel": "support@example.com",
  "contact": {
    "email": "jane@example.com",
    "phone": "+44 7700 900123",
    "name": "Jane Doe"
  },
  "organization": {
    "name": "Example Ltd"
  },
  "conversation": {
    "subject": "Question about BCone",
    "customFields": {
      "product": "BCone"
    }
  },
  "message": {
    "text": "Can BCone be worn in the shower?"
  }
}
```

The mapping is:

| Form field    | Request property                    |
| ------------- | ----------------------------------- |
| Your Name     | `contact.name`                      |
| Phone Number  | `contact.phone`                     |
| Your Email    | `contact.email`                     |
| Your Company  | `organization.name`                 |
| Subject       | `conversation.subject`              |
| Product       | `conversation.customFields.product` |
| Your Question | `message.text`                      |

The custom field must already be configured for the tenant and must accept the
provided value. CXP validates it before intake, then applies it through the
standard Conversation custom-field update after the Contact, Conversation, and
customer message are durable.

## Contact and organization resolution

CXP resolves a contact using the supplied contact ID, email, and phone evidence.
Those values must identify one compatible tenant contact; ambiguous or
conflicting evidence returns `409 ContactIdentityConflict`.
The reserved `anonymous` Contact ID cannot be supplied. A non-empty phone value
must be valid; CXP normalizes it to E.164 or returns `400 BadRequest` instead of
discarding it.

When a contact exists, every supplied contact value is authoritative:

* A different supplied email, phone, name, first name, or last name replaces
  the current value.
* A value that already matches causes no write.
* An omitted optional value remains unchanged.

Use `contact.firstName` and `contact.lastName` for the standard structured
Contact properties. If you provide both without `contact.name`, CXP also sets
the Contact display name to `{firstName} {lastName}`. An explicit
`contact.name` takes precedence:

```json theme={null}
{
  "intakeChannel": "support@example.com",
  "contact": {
    "email": "jane@example.com",
    "firstName": "Jane",
    "lastName": "Doe"
  },
  "message": {
    "text": "Please contact me about your product."
  }
}
```

If no contact matches, CXP creates it. You do not need a create-or-update flag.

For an organization, you may provide `organizationId`, `name`, or both. CXP
uses the ID when it resolves to a tenant organization; otherwise it resolves
an exact normalized name and creates the organization when no exact match
exists. CXP associates the resolved organization with both the contact and the
conversation. It does not use fuzzy matching or rename an organization
implicitly.

## Assign the resolved conversation

You can request an agent or team assignment after CXP resolves or creates the
conversation. `intakeChannel` remains required even when you provide an
assignee.

To assign it to a team by exact name:

```json theme={null}
{
  "intakeChannel": "support@example.com",
  "contact": {
    "email": "jane@example.com"
  },
  "conversation": {
    "subject": "Account access",
    "assignee": {
      "type": "team",
      "name": "Technical Support"
    }
  },
  "message": {
    "text": "Please help with account access."
  }
}
```

Use `type: "agent"` or `type: "team"`, then provide exactly one of `id` or
`name`. A name must match one active eligible agent or team exactly after
normalization. Prefer `id` when you have it because it provides the most direct
and lowest-latency lookup.

Assignment and Assistant processing follow these rules:

1. If the request contains `conversation.assignee`, CXP applies it only when
   the resolved conversation has no assignee. An existing assignee is never
   replaced.
2. Supplying an assignee always prevents an AI Assistant turn for that request,
   even when CXP preserves an existing assignee instead of applying the
   requested one.
3. If the request omits an assignee and the resolved conversation is
   AI-handled and unassigned, CXP starts an asynchronous turn using the AI
   Assistant configured for `intakeChannel`.
4. If the resolved conversation is assigned to an agent or team, CXP sends the
   normal addressed customer-message notification to that existing or newly
   applied owner and does not start an Assistant turn.
5. For a directly assigned agent, CXP also schedules the normal internal Agent
   Assist lifecycle when Agent Assist is configured. This is separate from the
   customer-facing AI Assistant turn and does not delay the API response.

Owner notifications and Agent Assist startup are asynchronous post-commit
work. A successful response confirms that the conversation and customer
message are durable; it does not wait for realtime notification delivery.

An invalid or inactive `intakeChannel`, or a channel without an active AI
Assistant, returns an error. There is no All Agents fallback.

## Understand Simple-mode append behavior

In Simple mode, canonical resolution selects the contact's newest non-Closed
conversation and CXP then applies the request updates and adds the customer
message.

* If the conversation is assigned to an agent or team, CXP sends the normal
  addressed customer-message notification to that owner. A requested assignee
  cannot replace it. A directly assigned agent also retains the normal internal
  Agent Assist lifecycle when configured.
* If the conversation has no assignee, a requested assignee is applied and no
  AI Assistant turn starts.
* If the request has no assignee and an AI Assistant handles the unassigned
  conversation, CXP starts a turn using the Assistant configured for the
  required `intakeChannel` after the new message is durable.
* If the existing status is Pending or Resolved, CXP reopens it to Open. A
  Closed conversation is not reused.

For a new conversation, canonical resolution first performs the normal
Assistant conversation initialization, including the durable internal context
required by the Assistant. CXP then stores the API-created customer message as
a public user email message with `messageSource: "public_api"`.
`intakeChannel` is resolution and processing context; it is not a property of
the conversation or customer message.

The same subject rule applies when Simple mode resolves an existing
subjectless conversation: a supplied subject wins; otherwise CXP sets
`Message from {contact.email}`. A non-empty existing subject remains intact.

## Understand asynchronous Assistant email

When the request omits an assignee and the resolved conversation is AI-handled
and unassigned, the success response returns as soon as the contact,
conversation, and customer message are durable. It does not wait for the
Assistant's answer.

The Assistant processes the durable customer message asynchronously. If it
answers by email, CXP sends a new email from the effective service address to
the contact and creates the real email thread from that outbound message. The
API intake itself does not fabricate an inbound email thread.

## Read the success response

```json theme={null}
{
  "conversation": {
    "conversationId": "CX123",
    "result": "created",
    "subject": "Question about BCone",
    "status": "open",
    "priority": "normal",
    "channel": "email",
    "serviceEmail": "support@example.com",
    "assignee": null,
    "organizationId": "org_demo_123"
  },
  "contact": {
    "contactId": "ct_demo_123",
    "result": "created",
    "email": "jane@example.com",
    "name": "Jane Doe",
    "phone": "+447700900123",
    "organizationId": "org_demo_123"
  }
}
```

`conversation.result` is `created` or `appended`. `contact.result` is:

* `created`: CXP created the contact.
* `updated`: at least one supplied contact value changed.
* `matched`: all supplied values already matched, so no contact write was
  needed.

`conversation.assignee` is `null` for an AI-handled, unassigned conversation.
Otherwise it reports the preserved or newly applied agent or team. The
response does not include the customer message ID/body, Assistant identity,
notification status, Assistant turn status, Agent Assist lifecycle status, or
email delivery status.

### Handle a custom-field warning

If the Conversation and customer message are created or appended successfully
but the separate custom-field update fails, CXP still returns `200 OK` or
`201 Created`. The response includes:

```json theme={null}
{
  "conversation": {
    "conversationId": "CX124",
    "result": "created",
    "subject": "Question about BCone",
    "status": "open",
    "priority": "normal",
    "channel": "email",
    "serviceEmail": "support@example.com",
    "assignee": null,
    "organizationId": null
  },
  "contact": {
    "contactId": "ct_demo_124",
    "result": "created",
    "email": "jane@example.com",
    "name": "Jane Doe",
    "phone": null,
    "organizationId": null
  },
  "warnings": [
    {
      "code": "CustomFieldsUpdateFailed",
      "message": "Conversation intake succeeded, but its custom fields could not be updated."
    }
  ]
}
```

The Contact, Conversation, and customer message remain successful. CXP also
starts any eligible asynchronous Assistant turn. Record or alert on the
warning so the custom-field configuration or persistence failure can be
investigated; do not retry the entire Create Conversation request solely to
repair custom fields, because every retry creates new intake work.

## Handle errors and retries

Common endpoint errors include:

| HTTP status | Code                                                                                   | What to do                                                           |
| ----------- | -------------------------------------------------------------------------------------- | -------------------------------------------------------------------- |
| 400         | `BadRequest` or `InvalidCustomField`                                                   | Fix the body, unknown property, field limit, or custom field value.  |
| 401         | `Unauthenticated`                                                                      | Supply a valid, unexpired tenant API key.                            |
| 403         | `PermissionDenied`                                                                     | Check tenant ownership and the conversation-write scope.             |
| 404         | `EmailChannelNotFound`, `AssigneeNotFound`, or `NotFound`                              | Check the tenant resource selector.                                  |
| 409         | `EmailChannelDisabled`                                                                 | Activate the service email and its configured Assistant.             |
| 409         | `ContactIdentityConflict`, `OrganizationAmbiguous`, `AssigneeAmbiguous`, or `Conflict` | Resolve the conflicting or ambiguous tenant data.                    |
| 413         | `PayloadTooLarge`                                                                      | Keep the body at or below 1 MiB and fields within documented limits. |
| 429         | `RateLimitExceeded`                                                                    | Wait for `Retry-After` before another call.                          |
| 502, 504    | `BadGateway`, `GatewayTimeout`                                                         | Treat the mutation outcome as unknown; a retry can duplicate it.     |
| 503         | `ServiceUnavailable`                                                                   | Retry later, understanding that every accepted POST executes.        |

<Warning>
  The endpoint has no request-idempotency header or duplicate suppression.
  Repeated successful calls add repeated effects. A tracing request ID does not
  change this behavior.
</Warning>

For all fields, limits, examples, and response schemas, open **API reference**
in the developer navigation.
