> This page is for Omni Channel.

> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.hams.ai/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.hams.ai/_mcp/server.

# Conversations

> Read, create, label, assign, and change the status of the conversations of your integration.

All the endpoints on this page are under `/api/v1/accounts/{account_id}/developer/conversations`. They take the same `api_access_token` header as the send endpoint.

## Which conversation id to use

Every endpoint on this page takes the conversation number in the path. This number is the `display_id` of the conversation. It is the number in the dashboard URL: in `/app/accounts/1/conversations/1234`, the number is `1234`. It is also the `conversation_id` that the send endpoint returns.

The conversation also has an internal `id`. The developer API does not take the internal `id`. The workflow trigger and the workflow webhook take the internal `id`, not the dashboard number. To get the internal `id` of a conversation, use [Pre-call context](/omni-channel/api/precall-context). See [Workflows](/omni-channel/workflows).

## Read a conversation

```bash
curl https://omni-channel.hams.ai/api/v1/accounts/1/developer/conversations/1234 \
  -H "api_access_token: $HAMS_TOKEN"
```

The response has these fields, and more:

| Field                | Meaning                                                                                                                                                        |
| -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`                 | The conversation number in the dashboard URL (`display_id`). It is not the internal `id`                                                                       |
| `inbox_id`           | The id of the inbox                                                                                                                                            |
| `status`             | `open`, `resolved`, `pending`, or `snoozed`                                                                                                                    |
| `can_reply`          | `true` when the service window is open and the API accepts plain `content`                                                                                     |
| `priority`           | `urgent`, `high`, `medium`, `low`, or `null`                                                                                                                   |
| `labels`             | The labels of the conversation                                                                                                                                 |
| `custom_attributes`  | The custom attributes of the conversation                                                                                                                      |
| `snoozed_until`      | The time when a snoozed conversation opens again                                                                                                               |
| `meta.sender`        | The contact of the conversation                                                                                                                                |
| `meta.assignee`      | The agent or the AI agent that owns the conversation, if it has one                                                                                            |
| `meta.assignee_type` | `User` for an agent, `Hams::Agent::Assistant` for an AI agent, or `AgentBot` for another bot. A conversation that an AI agent or a bot owns counts as assigned |
| `meta.team`          | The team of the conversation, if it has one                                                                                                                    |
| `messages`           | A list with the last message of the conversation                                                                                                               |

Read `can_reply` before you send plain `content`. If it is `false`, send a template. See [Messages](/omni-channel/api/messages).

## Create a conversation

```
POST /api/v1/accounts/{account_id}/developer/conversations
```

```json
{
  "inbox_id": 5,
  "contact_id": 42,
  "status": "open",
  "message": { "content": "Hi, we received your request." }
}
```

`inbox_id` and `contact_id` are necessary. The optional fields are `message`, `status`, `assignee_id`, `team_id`, `custom_attributes`, `additional_attributes`, and `snoozed_until`. An `assignee_id` or a `team_id` that is not in your account returns `404`.

If the inbox keeps 1 conversation for each contact, the API returns the latest conversation of the contact and does not create a new one. This endpoint does not check the service window for `message`. For most integrations, the [send endpoint](/omni-channel/api/messages) is better. It creates the conversation when necessary, and it checks the window.

## Reply in a conversation

```
POST /api/v1/accounts/{account_id}/developer/conversations/{conversation_id}/messages
```

```json
{ "content": "It is on its way." }
```

This endpoint does not check the service window. Use the [send endpoint](/omni-channel/api/messages) with `conversation_id` for a reply to the customer. It takes the same input and checks the window first.

Use this endpoint for a private note. Agents see a private note, and the customer never gets it:

```json
{ "content": "The customer called about this too.", "private": true }
```

The message has the name of the key as its sender. A request cannot set another sender.

## Change the status

```
POST /api/v1/accounts/{account_id}/developer/conversations/{conversation_id}/toggle_status
```

```json
{ "status": "resolved" }
```

The values are `open`, `resolved`, `pending`, and `snoozed`. For `snoozed`, also send `snoozed_until` as a Unix timestamp in seconds. Do not send a date string such as ISO 8601. If you send no `status`, an open conversation becomes resolved, and any other conversation becomes open.

The response gives the new status:

```json
{
  "meta": {},
  "payload": { "success": true, "conversation_id": 1234, "current_status": "resolved", "snoozed_until": null }
}
```

A resolved conversation changes where the next message goes. The next send to that contact opens a new conversation. It does not continue the resolved one. If the inbox keeps 1 conversation for each contact, the next send continues the resolved conversation.

## Priority, labels, assignment, and custom attributes

#### Priority

`POST .../toggle_priority` with `{"priority": "urgent"}`. The values are `urgent`, `high`, `medium`, and `low`. Send `null` to remove the priority. The response has no body. `PATCH .../{conversation_id}` with `priority` does the same and returns the conversation.

#### Labels

`GET .../labels` returns the labels. `POST .../labels` with `{"labels": ["vip", "refund"]}` replaces all the labels. Send every label that you want to keep.

#### Assignment

`POST .../assignments` with `{"assignee_id": 7}` for an agent, or `{"team_id": 3}` for a team. Send `{"assignee_id": null}` to remove the agent.

#### Custom attributes

`POST .../custom_attributes` with `{"custom_attributes": {"crm_id": "9912"}}`. The object replaces all the custom attributes of the conversation.

> **Warning**
>
> Send only an agent id or a team id from your account. If the API does not find the `assignee_id` in your account, it removes the agent from the conversation. If it does not find the `team_id`, it removes the team. It does not return an error.

When both `assignee_id` and `team_id` are in 1 assignment request, the API uses `assignee_id` only. The response is the agent, the team, or `null`.

## Related

* [Messages](/omni-channel/api/messages)
* [Contacts](/omni-channel/api/contacts)
* [Pre-call context](/omni-channel/api/precall-context)
* [Errors](/omni-channel/api/errors)