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

# Conversations

> Read the conversations of a tenant, answer in them, and move them between the agent and a person.

A conversation is a thread between one customer and the business on one channel. The routes below work on every channel.
Some actions depend on the channel, and each conversation says what it allows.

## List conversations

```bash theme={null}
curl "https://api.impellabs.tech/api/v1/partner/tenants/acme-001/conversations/?state=active&limit=50" \
  -H "Authorization: Bearer implpk_..."
```

| Parameter | Use |
| - | - |
| `state` | `human` (default) lists conversations that a person has taken over. Other values: `active`, `waiting_reply`, `closed`, `all`. |
| `channel` | `whatsapp`, `messenger`, `instagram` or `website`. |
| `updated_since` | An ISO 8601 time. Keeps conversations that changed after it. |
| `limit` | 1 to 200. Default 50. |
| `after` | The `next_cursor` of the page before. |

The list starts with the oldest update. Read from the last time you stopped with `updated_since`, or follow
`next_cursor`. Needs the `messages:read` scope.

## The conversation object

```json theme={null}
{
  "id": "5d2f7a3e-0000-4000-8000-000000000000",
  "object": "conversation",
  "status": "active",
  "channel": "whatsapp",
  "contact": { "name": "Sam", "phone": "+15550100101", "lead_id": "8a1e0c7b-0000-4000-8000-000000000000" },
  "human_control": { "on": false, "state": "ai", "reason": "", "since": "", "pending_since": "" },
  "window": { "open": true, "last_inbound_at": "2026-01-15T10:04:00+00:00", "closes_at": "2026-01-16T10:04:00+00:00" },
  "capabilities": { "can_text": true, "can_template": true, "can_media": true, "window_open": true, "tags": [] },
  "whatsapp_number": "100000000000001"
}
```

| Field | Meaning |
| - | - |
| `status` | `active`, `waiting_reply`, `human` (a person has it) or `closed`. |
| `human_control.state` | `ai`, `pending` (the customer asked for a person) or `human`. |
| `window` | Whether you can send free text now. A channel with no window is always open. |
| `capabilities` | What a person can send now. Use it to switch off a reply box. |
| `contact.lead_id` | The id of the contact. On Messenger and Instagram, `contact.customer_id` holds the customer id. |

## Read messages

`GET /api/v1/partner/tenants/{external_ref}/conversations/{session_id}/messages/` lists the messages, oldest first. Use
`limit` (1 to 100, default 50) and `after`.

A message has `role` (`user`, `assistant` or `human`), `type` (`text`, `image`, `template`, `reaction` and the other types
that the channel carries), `text`, and a `channel` block with the provider message id, the sender or receiver, and the
`status` of a message that went out. An edit changes the text of the message it edits. A revoked message has
`deleted_at`. A message from before the number was connected has `history` set to `true`. A message that the business
typed on its own phone has `source` set to `business_app`.

A message with a file has `channel.media.ref`. Fetch the file with
`GET /api/v1/partner/tenants/{external_ref}/media/{media_ref}/`. The answer is the file.

## Send a message

```bash theme={null}
curl -X POST https://api.impellabs.tech/api/v1/partner/tenants/acme-001/conversations/5d2f7a3e-0000-4000-8000-000000000000/messages/send/ \
  -H "Authorization: Bearer implpk_..." \
  -H "Content-Type: application/json" \
  -d '{ "type": "text", "text": "Your visit is at 3 pm." }'
```

The answer is 201 with `message` and `conversation`. Needs the `messages:write` scope.

| `type` | Fields |
| - | - |
| `text` (default) | `text` |
| `template` | `template_name`, `lang` (default `en_US`), `body_params`, `components` |
| `image`, `video`, `audio`, `document` | `link` or `media_id`, and optionally `caption` and `filename` |
| `interactive`, `location` | `text` and `payload` |
| `reaction` | `payload.emoji` (or `text`) and `reply_to` |

Add `reply_to` to quote a message. A send is the reply of the business owner. If the agent pauses when a person replies,
the reply takes the conversation over, and the agent can come back after the time that you set on the agent. A closed
conversation opens again.

Rules that depend on the channel:

* On WhatsApp, Messenger and Instagram you can send free text for 24 hours after the last customer message. After that the call
  answers 422 `outside_customer_service_window`, and the error holds the `window`. On WhatsApp, send a template. See
  [Templates](https://docs.impellabs.tech/partners/templates.md).
* On Messenger, a person can also reply for seven days with `"tag": "HUMAN_AGENT"`.
* Messenger, Instagram and website chat take text only. Another type answers 422 `text_only`.
* A customer who opted out answers 409 `opted_out`.
* A refusal by the provider answers 502 `channel_rejected`.

## Message a customer first

```bash theme={null}
curl -X POST https://api.impellabs.tech/api/v1/partner/tenants/acme-001/messages/send/ \
  -H "Authorization: Bearer implpk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "to": "+15550100101",
    "channel": "whatsapp",
    "type": "template",
    "template_name": "appointment_reminder",
    "body_params": ["Sam", "3 pm"]
  }'
```

`channel` is `whatsapp` (default), `messenger` or `instagram`. On WhatsApp the call opens a conversation, or uses the open
one for that number. Send `whatsapp_number` to pick the number to send from. On Messenger and Instagram the
conversation must already exist, and a call without one answers 409 `no_such_thread`. The body is the same as the send
body above.

## Take over and give back

`POST .../conversations/{session_id}/handoff/` with `{ "on": true, "reason": "billing question" }` puts a person in
charge. The agent stays silent. `{ "on": false }` gives the conversation back to the agent. The answer is the
conversation. Needs `messages:write`.

`POST .../conversations/{session_id}/resume/` gives the conversation back to the agent and answers
`{ "id", "status", "resumed" }`. `resumed` is `false` when nobody had taken it. Needs `assistants:write`.

`POST .../conversations/{session_id}/turns/` makes the agent answer now. Send `text` as an instruction. The answer is 202
`{ "queued": true }`. While a person has the conversation, the call answers 409 `human_control`. Send `"force": true` to
give the conversation back first. A closed conversation answers 409.

## Read receipts and typing

`POST .../conversations/{session_id}/read/` marks the last customer message as read. Call it when a person opens the
thread. It is never automatic. `POST .../typing/` shows a typing indicator and marks the message as read. Both take an
optional `message_id`. A conversation with no customer message answers 409. The answer is `{ "ok", "message_id" }`.
Both need `messages:write`.

## Events

ImpelLabs sends `message.*` events and `conversation.created`, `conversation.completed` and `handoff.requested` to your
[webhook endpoint](https://docs.impellabs.tech/partners/webhooks.md), so you do not need to poll.


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