# Server-side chat API

> Create chat sessions, post customer and agent messages, show typing indicators, hand off bot conversations, and record CSAT from your backend with an API key, using the v2.1 chat endpoints.

Source: https://developer.kustomer.com/docs/chat/server-chat-api

The chat SDKs act as a customer, from a browser or device. The **v2.1 chat API** does the same jobs from your server with a regular API key, which is what you need when chat runs through something you host: your own bot, a messaging bridge, or an AI agent that answers before a human.

Every route takes the customer in the request (`customerId`) rather than a chat SDK token, and the messages show up in the customer's chat on every device, like any other chat message.

| You want to                                | Endpoint                                                                                                                           |
| ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------- |
| Open a chat conversation for a customer    | [`POST /v2.1/chat/sessions`](https://developer.kustomer.com/api/chat/create-a-chat-session)                                        |
| Import a conversation your bot already had | [`POST /v2.1/chat/handoff`](https://developer.kustomer.com/api/chat/create-a-chat-session-via-handoff)                             |
| Post a message as the customer             | [`POST /v2.1/chat/messages`](https://developer.kustomer.com/api/chat/create-an-inbound-chat-message)                               |
| Post a message as an agent                 | [`POST /v2.1/chat/outbound/messages`](https://developer.kustomer.com/api/chat/create-an-outbound-agent-chat-message)               |
| Show or hide a typing indicator            | [`POST /v2.1/chat/outbound/typing`](https://developer.kustomer.com/api/chat/show-or-hide-agent-ai-typing-indicator)                |
| Post assistant (bot) messages              | [`POST /v2.1/chat/outbound/assistant/messages`](https://developer.kustomer.com/api/chat/create-an-outbound-assistant-chat-message) |
| List a customer's chat conversations       | [`GET /v2.1/chat/sessions`](https://developer.kustomer.com/api/chat/list-chat-sessions)                                            |
| Read a conversation's messages             | [`GET /v2.1/chat/sessions/{id}/messages`](https://developer.kustomer.com/api/chat/list-messages-for-a-chat-session)                |
| Record a CSAT rating or comment            | [`PUT /v2.1/chat/satisfaction-responses/{id}`](https://developer.kustomer.com/api/chat/update-a-chat-satisfaction-response)        |

## Roles

| Endpoint            | Role                                                                     |
| ------------------- | ------------------------------------------------------------------------ |
| Create session      | `org.permission.conversation.create`                                     |
| Handoff             | `org.permission.conversation.create` and `org.permission.message.create` |
| Messages and typing | `org.permission.message.create`                                          |
| List sessions       | `org.permission.conversation.read`                                       |
| List messages       | `org.permission.message.read`                                            |
| CSAT                | `org.permission.conversation.update`                                     |

Use a server-side API key with these roles, never a chat key. See [API keys and roles](https://developer.kustomer.com/docs/get-started/api-keys-and-roles).

## Answer chats with your own agent

A common pattern: your service (a bot or an AI agent) answers customers in chat, and hands off to Kustomer routing when it can't help.

```mermaid
sequenceDiagram
  autonumber
  participant C as Customer (chat SDK)
  participant K as Kustomer
  participant S as Your service
  C->>K: Sends a message
  K-->>S: Webhook or workflow: kustomer.message.create
  S->>K: POST /v2.1/chat/outbound/typing (typing: true)
  S->>S: Work out the answer
  S->>K: POST /v2.1/chat/outbound/messages (auto: true)
  S->>K: POST /v2.1/chat/outbound/typing (typing: false)
  K-->>C: Reply and typing state, in realtime
```

#### Hear about new messages

Subscribe to `kustomer.message.create` with an [outbound webhook](https://developer.kustomer.com/docs/events/outbound-webhooks), or call your service from a [workflow](https://developer.kustomer.com/docs/workflows). Only act on inbound chat messages: check the message's channel is `chat` and its direction is `in`.

#### Show that you're typing

```bash title="cURL"
curl -X POST "https://acme.api.kustomerapp.com/v2.1/chat/outbound/typing" \
  -H "Authorization: Bearer $KUSTOMER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"customerId": "6716a3a9c4e5f60012ab34b0", "session": "6716a3a9c4e5f60012ab34c1", "from": "5f1b2c3d4e5f600012ab0001", "typing": true, "ai": true}'
```

`ai: true` shows the AI typing indicator instead of the agent one. You own the indicator's lifecycle: sending a message doesn't hide it, so send `typing: false` when you're done, and repeat `typing: true` about every three seconds for long answers.

#### Reply

```bash title="cURL"
curl -X POST "https://acme.api.kustomerapp.com/v2.1/chat/outbound/messages" \
  -H "Authorization: Bearer $KUSTOMER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "customerId": "6716a3a9c4e5f60012ab34b0",
    "session": "6716a3a9c4e5f60012ab34c1",
    "from": "5f1b2c3d4e5f600012ab0001",
    "body": "Your order shipped this morning. Tracking: 1Z999AA10123456784",
    "auto": true
  }'
```

`from` is the Kustomer user the message is attributed to; use a dedicated user for your bot. `auto: true` marks the message as automated, so it doesn't count as the first human response in your reporting.

#### Hand off

When a human should take over, stop replying and let routing assign the conversation, for example by updating a conversation attribute that your queue rules match on. A [workflow](https://developer.kustomer.com/docs/workflows/triggers-and-actions) can make that update for you.

## Create a session

`POST /v2.1/chat/sessions` ([reference](https://developer.kustomer.com/api/chat/create-a-chat-session))

Opens a new chat conversation for an existing customer, for example to start a proactive chat:

```json title="Body"
{
  "customerId": "6716a3a9c4e5f60012ab34b0",
  "title": "Your order is delayed",
  "brand": "5f1b2c3d4e5f600012ab0099"
}
```

`trackingId` links the session to a chat SDK identity. Leave it out and Kustomer reuses the one from the customer's latest chat session, if any. When you pass `assistant`, Kustomer also starts that assistant's opening dialog in the new session.

## Import a bot conversation

`POST /v2.1/chat/handoff` ([reference](https://developer.kustomer.com/api/chat/create-a-chat-session-via-handoff))

If a conversation started in your own bot, import the whole thing so agents see the history:

```json title="Body"
{
  "customerId": "6716a3a9c4e5f60012ab34b0",
  "active": true,
  "from": "Acme Bot",
  "externalId": "bot-thread-88123",
  "messages": [
    { "date": "2026-10-21T15:00:02Z", "direction": "in", "body": "Where's my order?" },
    { "date": "2026-10-21T15:00:04Z", "direction": "out", "body": "I can help. What's your order number?" },
    { "date": "2026-10-21T15:00:31Z", "direction": "in", "body": "4417, and I need to talk to a person" }
  ]
}
```

| Field        | Rule                                                                                                                                                                                        |
| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `messages`   | 2 to 100 messages, with at least one `in` and one `out`. Sorted by `date`. Bodies up to 10,240 characters.                                                                                  |
| `from`       | Applied to every outbound message                                                                                                                                                           |
| `active`     | `true` creates the conversation open, ready for routing. `false` creates it closed, as a record only.                                                                                       |
| `externalId` | Stored on the session so you can link back to your bot's thread                                                                                                                             |
| `subStatus`  | Optional conversation substatus ID. It's an open substatus when `active` is `true`, and a done substatus when `active` is `false`.                                                          |
| `trackingId` | Optional tracking identity ID to attach to the session. Left out, Kustomer may reuse the customer's latest chat session tracking ID, so omitting it doesn't guarantee an untracked session. |

The chat SDKs have the same feature as `handoff()`, for imports that happen in the customer's browser or app.

## Customer messages from your server

`POST /v2.1/chat/messages` ([reference](https://developer.kustomer.com/api/chat/create-an-inbound-chat-message))

Posts a message as the customer, for bridges that receive customer messages somewhere else. `customerId` must match the session's customer. Send `body`, `attachments`, or both.

## Read chats

```bash title="List a customer's open chats"
curl "https://acme.api.kustomerapp.com/v2.1/chat/sessions?customerId=6716a3a9c4e5f60012ab34b0&active=true" \
  -H "Authorization: Bearer $KUSTOMER_API_KEY"
```

Pass `customerId`, `trackingId`, or both (sessions matching either are returned); with neither you get a `400`. Results are newest first by `lastMessageAt`. Session metadata doesn't include messages: fetch those with `GET /v2.1/chat/sessions/{id}/messages`, using `fields` to limit the attributes returned.

## CSAT

`PUT /v2.1/chat/satisfaction-responses/{id}` ([reference](https://developer.kustomer.com/api/chat/update-a-chat-satisfaction-response))

Records a rating, a comment, or both on a CSAT response that Kustomer already created for the chat:

| Body                                 | Result                |
| ------------------------------------ | --------------------- |
| `rating` only                        | Status **rated**      |
| `answers` (with or without `rating`) | Status **commented**  |
| `read: true`                         | Marks the survey read |

`rating` is 0 to 10, and `answers` takes up to three answers. Add `?include=satisfaction` to get the form back, with the follow-up questions for that rating.
