Developer Docs Logo
API RequestsAPI SchemaChats

Create Conversation

POST/v1/conversations

Creates a new conversation on behalf of a customer contact via an external integration. The first message is always sent as the customer (contact) — agent-initiated messages are not supported. Message type is always plain text.

Either contact_id or contact_name must be provided:

  • If contact_id is provided, the contact must already exist and belong to the authenticated workspace.
  • If contact_id is omitted, a new contact is created using contact_name.

Requires chat:write scope.

Authorization

BearerAuth
AuthorizationBearer <token>

In: header

Request Body

application/json

Conversation creation details. Provide either contact_id (existing contact) or contact_name (creates a new contact).

TypeScript Definitions

Use the request body type in TypeScript.

Request body for creating a conversation from an external source. Provide either contact_id to use an existing contact, or contact_name to create a new one.

Response Body

application/json

application/json

application/json

application/json

application/json

application/json

application/json

curl -X POST "https://example.com/v1/conversations" \  -H "Content-Type: application/json" \  -d '{    "channel_id": "a24f8ef9-2da9-47e0-9be5-0dbeb40a8d54",    "contact_id": "550e8400-e29b-41d4-a716-446655440000",    "message": "Hi, I need help with my order #12345."  }'
{  "request_id": "550e8400-e29b-41d4-a716-446655440003"}

Create Attachment Upload URL POST

Requests a presigned URL for uploading an attachment file. The client uploads the file directly to the returned `upload_url` via HTTP PUT, then references the returned `attachment_id` when sending an attachment message. The upload URL is single-use, expires after ~5 minutes, and pins both `Content-Type` and `Content-Length` to the values declared in this request. Requires `chat:write` scope.

Send Conversation Message POST

Sends a message to a conversation. The message is enqueued for asynchronous delivery. Supports plain text messages and forms. Requires `chat:write` scope. ### Closing the conversation in the same call Set `transition_status_to` to close the conversation right after this message is delivered — for a goodbye note, or a "this chat has ended" notice. Use this rather than sending the message and then calling `PUT /v1/conversations/{id}/status` yourself: as two separate requests they can overtake each other. | Value | Meaning | |---|---| | `RESOLVED` | Closed, but the contact reopens it by sending another message. | | `FINALIZED` | Permanently closed. You will need a new conversation next time. | | `OPEN` | Not accepted. Sending any message into a resolved conversation reopens it already. | | Rule | Behavior | |---|---| | Ordering | The message always lands first — the conversation is only closed once the message is persisted, so the customer never sees a closed chat missing your last message. | | Response | Still `202`, immediately. The close happens moments later in the background; the response acknowledges the message, not the close. | | End state | The one you asked for, whatever the conversation was doing beforehand. If it was already closed, the message reopens it and it closes again — agents may see a brief reopen in the history. If it was already in the requested state, nothing changes. | | `FINALIZED` from `OPEN` | Resolved first, then finalized — matching `PUT /v1/conversations/{id}/status`. | | Enablement | Requires the capability to be switched on for your workspace. Until it is, the field is **silently ignored**: the message is sent normally and returns `202`, but the conversation stays open. | | Already `FINALIZED` | A finalized conversation rejects new messages, so this cannot add a farewell to a chat that is already permanently closed. Create a new conversation instead. | | Invalid value | `400`, and no message is sent. (While the capability is off nothing is validated, so the same request returns `202` instead.) | **Best-effort, and a failure is silent.** The message is never affected — it is sent, stored and delivered regardless. But if the close itself fails (transient backend problem, or someone changed the status in the meantime) the conversation keeps its previous status and nothing tells you: the `202` was only ever about the message, and there is no webhook for a close that did not happen. If the final status matters, read it back with `GET /v1/conversations/{id}` and fall back to `PUT /v1/conversations/{id}/status`.