# Conversations and Messages

> List a Mojeeb agent's conversations, keep up with new messages, reply to customers as your team, switch the AI off in one conversation, mark conversations urgent or resolved, and assign them, with the Public API.

Every chat your agent has with a customer, on WhatsApp, Facebook, Instagram or your website, is a conversation. With the Public API your systems can read them and their messages, reply as your team, close or archive them, switch the AI off in one conversation, mark it urgent or resolved, and assign it to a teammate. Everything lives under `/v1/agents/{agentId}/conversations`.

## How do I list an agent's conversations?

`GET` them, the most recently active first. Filter by `status`, `channel`, `is_read`, `urgent`, `assigned_to`, `contact_id`, or by when the last message came with `active_since` and `active_before`:

```bash
curl "https://api.mojeeb.app/v1/agents/YOUR_AGENT_ID/conversations?is_read=false&channel=whatsapp" \
  -H "Authorization: Bearer mk_live_YOUR_KEY_HERE"
```

Each conversation carries its last message, who wrote it and when, whether your team has read it, its attention flags, the AI's state and its assignee, so you can tell which ones need you without opening them. `search` finds a customer's name or a word in the last message. A list filtered by status, channel, read state, urgency or assignee covers only a limited stretch of activity, ending at `active_before` or now, unless you send `active_since`, so the list stays fast on a large inbox; a contact's conversations always come back whole. A conversation with no message yet isn't listed. See the [List conversations reference](../api-reference/list-conversations) for the window.

## How do I keep up with new messages?

Poll the list, not each conversation. Every minute or so, list with `active_since` set a few minutes before the newest `last_message_at` you have seen, and page while `has_more` is true. Only conversations with a new message come back. For each one, read its messages with `after` set to a message a few minutes older than the newest one you have:

```bash
curl "https://api.mojeeb.app/v1/agents/YOUR_AGENT_ID/conversations/CONVERSATION_ID/messages?after=MESSAGE_ID" \
  -H "Authorization: Bearer mk_live_YOUR_KEY_HERE"
```

With `after`, messages come oldest first. While `has_more` is true, call again with `after` set to the last message you got. Keep both reads overlapping and drop what you already have by `id`: a message your team sends from the WhatsApp or Meta apps carries the platform's time, which can be a little earlier than when Mojeeb records it, so it can land before messages you have already read. A customer's photo, voice note, sticker or file sent without a caption doesn't move `last_message_at`, so the conversation only comes back once a later message, such as the AI's answer, moves it. A change of status, read state, flags or assignee doesn't bring a conversation back either; read it with `GET` ([Get a conversation](../api-reference/get-conversation)) when you need its state.

## How do I read a conversation's messages?

Without `after`, the newest messages come first, and `next_cursor` reads further back. Each message says who sent it: the customer, the AI, a user (a teammate, or a key writing as its creator), or Mojeeb on your behalf (a broadcast, a `/v1/whatsapp` send). Messages sent to the customer carry their delivery `status`. Attachment links expire; read the message again for a fresh one. See the [List messages reference](../api-reference/list-conversation-messages) for how long they last.

Customers write the message text. Treat it as untrusted input, especially before you hand it to an AI model: a customer can write instructions aimed at your assistant.

## How do I reply to a customer?

`POST` the text to the conversation's messages:

```bash
curl -X POST https://api.mojeeb.app/v1/agents/YOUR_AGENT_ID/conversations/CONVERSATION_ID/messages \
  -H "Authorization: Bearer mk_live_YOUR_KEY_HERE" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: reply-7f3a9c" \
  -d '{ "text": "Yes, we are open on Friday from 2 pm to 10 pm." }'
```

The reply goes out on the conversation's channel as your team. In the dashboard it shows as written by the person who created the key. When the agent's `ai_handoff_enabled` is on, the reply pauses the AI in that conversation for the handoff window, like any teammate's reply; when it is off, the AI keeps answering. See [Agent Settings](./agent-settings). When a pause ends, the AI may write once more if follow-ups are on for that channel; to prevent it, switch the AI off in the conversation or resume it. A reply never assigns the conversation to anyone.

The answer is `201` with the message, `queued`. It is sent right after, and its delivery `status` changes from there; follow it by reading the message again with `GET` on its `id`. The [Get a message reference](../api-reference/get-conversation-message) lists every status. Send an `Idempotency-Key` so a retry after a timeout never sends the reply twice; see [Idempotency](../idempotency). Replies have a rate limit of their own; see [Rate Limits](../rate-limits) and the [Reply reference](../api-reference/send-conversation-message).

## Why was my reply refused?

Before anything is saved, Mojeeb checks that the reply can reach the customer:

- `422 messaging_window_closed`: the customer hasn't written in the last 24 hours. WhatsApp, Facebook and Instagram deliver your free-form message only within 24 hours of the customer's last one. On WhatsApp, send an approved template instead; see [Send a WhatsApp Template](../whatsapp/send-template).
- `422 channel_not_supported`: Mojeeb can't send on this channel yet (TikTok).

A refused reply isn't stored for its `Idempotency-Key`, and for a short while that key answers `409 idempotency_key_in_progress`: once the cause has passed, send the reply again with a new key. See [Idempotency](../idempotency).

A website chat has no window: the reply is stored as `sent` and appears in the customer's open chat.

## How do I switch the AI off in one conversation?

`PATCH` the conversation with `ai_enabled`:

```bash
curl -X PATCH https://api.mojeeb.app/v1/agents/YOUR_AGENT_ID/conversations/CONVERSATION_ID \
  -H "Authorization: Bearer mk_live_YOUR_KEY_HERE" \
  -H "Content-Type: application/json" \
  -d '{ "ai_enabled": false }'
```

The AI stays off in that conversation until you send `"ai_enabled": true`, the same switch as in the dashboard. A teammate's reply only pauses the AI for a while, shown as `ai_paused_until`; to end that pause now, `POST` to the conversation's `resume-ai` ([Resume the AI reference](../api-reference/resume-conversation-ai)). To switch the AI off on a whole channel, see [Channels and Follow-ups](./channels-and-follow-ups).

## How do I mark a conversation urgent or resolved?

`PATCH` it with `urgent`. `true` is the dashboard's **Mark as Urgent**, and `false` is **Mark as Resolved**. Both clear `needs_attention`, and resolving also sets an unhappy `sentiment` to neutral, so the conversation stops showing as needing attention. The AI reads both flags again with each of its replies, so a mark lasts until the AI next answers in that conversation. See the [Update a conversation reference](../api-reference/update-conversation).

## How do I close, archive or mark a conversation read?

`PATCH` it with `status` (`open`, `closed` or `archived`) or `is_read`, alone or with other fields in the same call. `is_read` is for your team only: marking a conversation read never sends the customer a read receipt. A change to what the conversation already has writes nothing. See the [Update a conversation reference](../api-reference/update-conversation).

## How do I assign a conversation?

`POST` the teammate's `user_id` as `assigned_to` to the conversation's `assign`, or `"assigned_to": null` to unassign it. The teammate must be able to open the agent; find them with [Your Team](./team). The conversation's lead is assigned with it, and Mojeeb records the assignment as made by the person who created the key. See the [Assign a conversation reference](../api-reference/assign-conversation).

## Common questions

### Can I search inside messages?

No. Searching message history reads every message the agent has, so it stays in the dashboard, where someone starts it on purpose. Through the API, `search` matches only the customer's name and the last message. To find a person, look them up in [Contacts](./contacts-and-leads) and filter the conversations by `contact_id`.

### Can I send images or files?

Not yet: replies through the API are text; the [Reply reference](../api-reference/send-conversation-message) gives the length and how each channel delivers a long reply. On WhatsApp, a template can carry a header image, video or document.

### Do replies through the API count toward my message limit?

No. Your plan counts the AI's messages. Your team's replies don't count, whether they come from the dashboard or the API.

### Can I start a conversation?

Not with these endpoints: no call creates a conversation. On WhatsApp you can reach a customer first with a template (see [Send a WhatsApp Template](../whatsapp/send-template)): it lands in their conversation, and starts one if they have none yet.

### Which scopes do these calls need?

`conversations:read` to list conversations and read messages, and `conversations:write` to change, assign and reply. Every key holds them today; see [Authentication](../authentication).
