
# Mensagens e Conversas

A API de Mensagens permite que você envie uma mensagem para qualquer contato, leia uma conversa, corrija ou remova uma mensagem que você já enviou, reaja a uma, puxe um tópico completo de sessão de chat, exporte uma transcrição e marque chats como lidos ou não lidos — tudo sem abrir a caixa de entrada.

Todos os caminhos nesta página são relativos à URL base `https://api.youraiconnector.com/v1`. Cada solicitação precisa da sua chave de API — consulte [Autenticação](authentication.md) para obter a lista completa de formas de enviá-la. Os exemplos abaixo usam o cabeçalho `X-API-Key`, com um exemplo cURL mostrando também o formato de consulta `?apiKey=`.

> **Como funciona a entrega:** O envio de uma mensagem **não** aguarda que ela chegue ao destino. A API aceita sua mensagem, retorna imediatamente com um ID de mensagem e, em seguida, a entrega em segundo plano no canal do contato (WhatsApp, SMS, Instagram, etc.). Para rastrear se uma mensagem foi realmente entregue ou lida, escute as atualizações de status com [Webhooks](webhooks.md) — não faça polling. A resposta de envio apenas confirma que a mensagem foi aceita.

---

## Enviar uma mensagem

Existem duas maneiras de enviar. Escolha a que melhor se adapta à forma como você já identifica o contato:

- **Enviar por ID de contato** — você já conhece o ID do contato (por exemplo, você criou o contato através da API ou o obteve de um webhook). Use `POST /contacts/{contactId}/send-message`.
- **Enviar por identidade do contato** — você conhece o número de telefone do contato, ID do Instagram, etc., mas não o ID interno dele. Use `POST /contacts/send` e deixe a plataforma encontrar o contato correto.

Ambos enfileiram a mensagem da mesma forma e a entregam no canal em que o contato estiver. Você não escolhe um transporte — a plataforma roteia contatos do WhatsApp via WhatsApp, contatos de SMS via SMS, e assim por diante.

### Enviar por ID de contato

`POST /contacts/{contactId}/send-message`

| Campo | Obrigatório | Descrição |
|---|---|---|
| `body` | Sim | O texto da mensagem a ser enviada. |
| `mediaUrl` | Não | URL de um arquivo de mídia (imagem, documento, etc.) para anexar. |
| `mediaContentType` | Não | Tipo MIME da mídia anexada, por exemplo, `image/jpeg`. |
| `pauseBot` | Não | `true` pausa a IA para este contato conforme a mensagem é enviada — para quando um humano assume o atendimento. Veja [Pausar ou retomar a IA](#pause-or-resume-the-ai-for-one-contact). |
| `clearIncompleteReply` | Não | `true` descarta uma resposta do bot inacabada para que ela não seja retomada após sua mensagem. |

**cURL**

```bash
curl -X POST "https://api.youraiconnector.com/v1/contacts/contact123/send-message" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "body": "Hi! Your appointment is confirmed for tomorrow at 10:00."
  }'
```

**JavaScript**

```javascript
const res = await fetch(
  "https://api.youraiconnector.com/v1/contacts/contact123/send-message",
  {
    method: "POST",
    headers: {
      "X-API-Key": "YOUR_API_KEY",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      body: "Hi! Your appointment is confirmed for tomorrow at 10:00.",
    }),
  }
);
const data = await res.json();
console.log(data.messageId);
```

**Python**

```python
import requests

res = requests.post(
    "https://api.youraiconnector.com/v1/contacts/contact123/send-message",
    headers={
        "X-API-Key": "YOUR_API_KEY",
        "Content-Type": "application/json",
    },
    json={"body": "Hi! Your appointment is confirmed for tomorrow at 10:00."},
)
print(res.json()["messageId"])
```

**Resposta** (`200 OK`):

```json
{
  "success": true,
  "messageId": "aB3dE5fG7hI9jK1lM2nO",
  "contactId": "contact123",
  "channel": "whatsapp",
  "message": "Message created successfully. Delivery is being processed."
}
```

### Enviar por identidade do contato

`POST /contacts/send`

Use isto quando você não tiver o ID interno do contato. Forneça o `body` da mensagem mais **ou** um `contact_id`, **ou** um `channel` junto com o campo de identidade que corresponde a esse canal.

| Campo | Obrigatório | Descrição |
|---|---|---|
| `body` | Sim | O texto da mensagem a ser enviada. |
| `contact_id` | Não | ID de um contato existente. Quando definido, os campos de identidade abaixo não são necessários. |
| `channel` | Não | Canal para envio. Obrigatório quando `contact_id` não é fornecido. Um dos 14 canais de envio de saída: `whatsapp`, `whatsapp_web`, `sms`, `instagram`, `instagram_private`, `messenger`, `telegram`, `chat-widget`, `custom`, `email`, `line`, `imessage`, `linkedin`, `viber`. |
| `phone_number` | Não | Número de telefone do contato no formato internacional. Usado com `whatsapp`, `whatsapp_web` e `sms`. |
| `instagram_id` | Não | ID de usuário do Instagram do contato. Usado com `instagram`. |
| `messenger_id` | Não | ID de usuário do Messenger do contato. Usado com `messenger`. |
| `telegram_user_id` | Não | ID de usuário do Telegram do contato. Usado com `telegram`. |
| `media_url` | Não | URL de um arquivo de mídia para anexar. |
| `media_content_type` | Não | Tipo MIME da mídia anexada, por exemplo, `image/jpeg`. |

**Quais canais podem ser resolvidos por identidade.** Apenas seis dos 14 aceitam um campo de identidade em vez de um `contact_id`: `whatsapp`, `whatsapp_web` e `sms` são pesquisados por `phone_number`, `instagram` por `instagram_id`, `messenger` por `messenger_id`, e `telegram` por `telegram_user_id`. Os outros oito — `instagram_private`, `chat-widget`, `custom`, `email`, `line`, `imessage`, `linkedin` e `viber` — não possuem identidade pública para pesquisa, portanto, enviar por esses canais requer `contact_id`; passar apenas `channel` retorna um `400` informando que `contact_id` é necessário.

**cURL** (usando o formato de consulta `?apiKey=`)

```bash
curl -X POST "https://api.youraiconnector.com/v1/contacts/send?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "channel": "whatsapp",
    "phone_number": "+31612345678",
    "body": "Hi! Your appointment is confirmed."
  }'
```

**JavaScript**

```javascript
const res = await fetch("https://api.youraiconnector.com/v1/contacts/send", {
  method: "POST",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    channel: "whatsapp",
    phone_number: "+31612345678",
    body: "Hi! Your appointment is confirmed.",
  }),
});
const data = await res.json();
console.log(data.message_id, data.channel);
```

**Python**

```python
import requests

res = requests.post(
    "https://api.youraiconnector.com/v1/contacts/send",
    headers={
        "X-API-Key": "YOUR_API_KEY",
        "Content-Type": "application/json",
    },
    json={
        "channel": "whatsapp",
        "phone_number": "+31612345678",
        "body": "Hi! Your appointment is confirmed.",
    },
)
data = res.json()
print(data["message_id"], data["channel"])
```

**Resposta** (`201 Created`):

```json
{
  "success": true,
  "message_id": "aB3dE5fG7hI9jK1lM2nO",
  "contact_id": "contact123",
  "channel": "whatsapp"
}
```

> **Por que uma mensagem pode ser rejeitada:** Um contato com o modo "não perturbe" ou modo privado ativado não pode receber mensagens de saída — a solicitação falha com um `422`. Se nenhum contato corresponder ao ID ou identidade que você forneceu, você receberá um `404`.

---

## Listar mensagens de um contato

`GET /contacts/{contactId}/messages`

Retorna as mensagens de um contato, da mais recente para a mais antiga, com paginação baseada em cursor.

| Parâmetro de consulta | Obrigatório | Descrição |
|---|---|---|
| `limit` | Não | Tamanho da página. Padrão `50`, máximo `100`. |
| `cursor` | Não | O valor `next_cursor` de uma resposta anterior. Retorna mensagens mais antigas que o cursor. |
| `filter` | Não | Filtrar por tipo de conteúdo: `all` (padrão), `text`, `media` ou `tool_use`. |
| `direction` | Não | Filtrar por direção: `all` (padrão), `inbound` (recebido do contato) ou `outbound` (enviado por você). |

> **Nota sobre filtragem e paginação:** Os filtros `filter` e `direction` são aplicados a cada página após a leitura, portanto, uma página filtrada pode conter menos itens que `limit`. O `next_cursor` continua avançando pela conversa completa, então continue paginando até que `next_cursor` seja `null`.

**cURL**

```bash
curl "https://api.youraiconnector.com/v1/contacts/contact123/messages?limit=50&direction=inbound" \
  -H "X-API-Key: YOUR_API_KEY"
```

**JavaScript**

```javascript
const params = new URLSearchParams({ limit: "50", direction: "inbound" });
const res = await fetch(
  `https://api.youraiconnector.com/v1/contacts/contact123/messages?${params}`,
  { headers: { "X-API-Key": "YOUR_API_KEY" } }
);
const data = await res.json();
console.log(data.messages, data.next_cursor);
```

**Python**

```python
import requests

res = requests.get(
    "https://api.youraiconnector.com/v1/contacts/contact123/messages",
    headers={"X-API-Key": "YOUR_API_KEY"},
    params={"limit": 50, "direction": "inbound"},
)
data = res.json()
print(data["messages"], data["next_cursor"])
```

**Resposta** (`200 OK`):

```json
{
  "success": true,
  "contact_id": "contact123",
  "messages": [
    {
      "id": "aB3dE5fG7hI9jK1lM2nO",
      "body": "Hi! Thanks for reaching out.",
      "direction": "inbound",
      "channel": "whatsapp",
      "status": "delivered",
      "type": null,
      "timestamp": "2026-06-01T10:00:00.000Z",
      "media_url": null,
      "media_content_type": null,
      "bot_reply": false
    }
  ],
  "next_cursor": "cD4eF6gH8iJ0kL2mN3oP"
}
```

### Campos da mensagem

| Campo | Descrição |
|---|---|
| `id` | ID exclusivo da mensagem. |
| `body` | Conteúdo de texto da mensagem. |
| `direction` | `inbound` (recebida do contato) ou `outbound` (enviada pela sua conta). |
| `channel` | Canal no qual a mensagem foi enviada ou recebida (por exemplo, `whatsapp`, `sms`, `instagram`). |
| `status` | Status de entrega atual, por exemplo, `Created`, `sent`, `delivered`, `read`, `failed`. |
| `type` | Tipo de mensagem. Mensagens de texto simples têm um tipo `null`; a atividade da ferramenta de assistente automatizado é marcada como `tool_use`. |
| `timestamp` | Hora ISO 8601 em que a mensagem foi criada. |
| `media_url` | URL de um arquivo de mídia anexado, se houver. |
| `media_content_type` | Tipo MIME da mídia anexada, se houver. |
| `bot_reply` | `true` quando a mensagem foi gerada pelo assistente de IA. |
| `score` | Sua avaliação da mensagem: `1` polegar para cima, `-1` polegar para baixo, `0` quando não foi avaliada. Veja [Avaliar ou marcar uma mensagem com estrela](#rate-or-star-a-message). |
| `is_important` | `true` quando a mensagem foi marcada com estrela. |
| `is_deleted` | `true` quando a mensagem foi excluída. Mensagens excluídas permanecem na lista, mas seus `body` e `media_url` ficam vazios. |
| `reactions` | Reações com emoji na mensagem, de ambos os lados. Sempre um array — vazio quando não há nenhuma. Cada entrada tem `emoji`, `from_phone_number`, `from_me` (`true` quando a reação é sua) e `reacted_at`. |

---

## Listar sessões de chat

Uma sessão de chat é uma janela de conversa com um contato: ela abre quando eles começam a falar e fecha quando a conversa é concluída. As sessões são a forma como você pagina um longo histórico em conversas legíveis, em vez de uma lista interminável.

### Sessões recentes de todos os contatos

`GET /chat-sessions/recent`

Retorna as sessões que começaram nas últimas X horas, da mais recente para a mais antiga, em todos os contatos da conta.

| Parâmetro de consulta | Obrigatório | Descrição |
|---|---|---|
| `hours` | Sim | Quantas horas retroceder. Deve ser um número inteiro positivo. |
| `status` | Não | Retornar apenas sessões com este status: `ChatSessionOpened` ou `ChatSessionClosed`. |
| `limit` | Não | Número máximo de sessões a retornar. Padrão `100`, máximo `100`. |
| `includeMessages` | Não | `true` adiciona um array `messages` a cada sessão. Desativado por padrão porque torna a resposta muito maior. |

**cURL**

```bash
curl "https://api.youraiconnector.com/v1/chat-sessions/recent?hours=24&status=ChatSessionClosed" \
  -H "X-API-Key: YOUR_API_KEY"
```

**JavaScript**

```javascript
const params = new URLSearchParams({ hours: "24", status: "ChatSessionClosed" });
const res = await fetch(`https://api.youraiconnector.com/v1/chat-sessions/recent?${params}`, {
  headers: { "X-API-Key": "YOUR_API_KEY" },
});
const data = await res.json();
console.log(data.data.total_sessions, data.data.sessions);
```

**Python**

```python
import requests

res = requests.get(
    "https://api.youraiconnector.com/v1/chat-sessions/recent",
    headers={"X-API-Key": "YOUR_API_KEY"},
    params={"hours": 24, "status": "ChatSessionClosed"},
)
data = res.json()["data"]
print(data["total_sessions"], data["sessions"])
```

**Resposta** (`200 OK`):

```json
{
  "success": true,
  "data": {
    "hours_ago": 24,
    "total_sessions": 2,
    "sessions": [
      {
        "session_id": "session456",
        "contact_id": "contact123",
        "contact_name": "Jane Doe",
        "contact_phone": "+31612345678",
        "contact_email": "jane@example.com",
        "start_date_time": "2026-06-01T09:55:00.000Z",
        "end_date_time": "2026-06-01T10:20:00.000Z",
        "status": "ChatSessionClosed",
        "tag": "Booking enquiry"
      }
    ]
  }
}
```

### Todas as sessões de um contato

`GET /chat-sessions/{contactId}`

Retorna todas as sessões de chat de um único contato. Mesmos parâmetros `status`, `limit` e `includeMessages` acima — `hours` não se aplica aqui.

**cURL**

```bash
curl "https://api.youraiconnector.com/v1/chat-sessions/contact123?limit=20" \
  -H "X-API-Key: YOUR_API_KEY"
```

**Resposta** (`200 OK`):

```json
{
  "success": true,
  "data": {
    "contact_id": "contact123",
    "contact_name": "Jane Doe",
    "total_sessions": 2,
    "sessions": [
      {
        "id": "session456",
        "start_date_time": "2026-06-01T09:55:00.000Z",
        "end_date_time": "2026-06-01T10:20:00.000Z",
        "status": "ChatSessionClosed",
        "tag": "Booking enquiry"
      }
    ]
  }
}
```

> **Os nomes dos campos de ID da sessão diferem entre os dois endpoints.** A lista de sessões recentes chama-o de `session_id` (também contém os detalhes do contato, já que as sessões vêm de muitos contatos); a lista por contato chama-o de `id`. Qualquer um dos valores é o que você passa como `{sessionId}` ao buscar o tópico completo abaixo.

Quando `includeMessages=true`, cada sessão ganha um array `messages` cujas entradas contêm `id`, `body`, `direction`, `timestamp`, `type`, `channel` e `status`.

---

## Buscar um thread de sessão de chat

`GET /contacts/{contactId}/chat-sessions/{sessionId}/messages`

Uma sessão de chat agrupa as mensagens de um contato em uma única janela de conversa. Este endpoint retorna o tópico completo de uma única sessão, **do mais antigo para o mais recente**, juntamente com os metadados da sessão. Você pode encontrar os IDs de sessão de um contato através dos endpoints de sessões de chat.

**cURL**

```bash
curl "https://api.youraiconnector.com/v1/contacts/contact123/chat-sessions/session456/messages" \
  -H "X-API-Key: YOUR_API_KEY"
```

**JavaScript**

```javascript
const res = await fetch(
  "https://api.youraiconnector.com/v1/contacts/contact123/chat-sessions/session456/messages",
  { headers: { "X-API-Key": "YOUR_API_KEY" } }
);
const data = await res.json();
console.log(data.session, data.messages);
```

**Python**

```python
import requests

res = requests.get(
    "https://api.youraiconnector.com/v1/contacts/contact123/chat-sessions/session456/messages",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
data = res.json()
print(data["session"], data["messages"])
```

**Resposta** (`200 OK`):

```json
{
  "success": true,
  "contact_id": "contact123",
  "session": {
    "id": "session456",
    "status": "ChatSessionClosed",
    "start_date_time": "2026-06-01T09:55:00.000Z",
    "end_date_time": "2026-06-01T10:20:00.000Z",
    "tag": "Booking enquiry"
  },
  "messages": [
    {
      "id": "aB3dE5fG7hI9jK1lM2nO",
      "body": "Hi! Thanks for reaching out.",
      "direction": "inbound",
      "channel": "whatsapp",
      "status": "delivered",
      "type": null,
      "timestamp": "2026-06-01T09:55:00.000Z",
      "media_url": null,
      "media_content_type": null,
      "bot_reply": false
    }
  ]
}
```

O objeto `session` informa `status` (`ChatSessionOpened` enquanto ativo, `ChatSessionClosed` após encerrado), `start_date_time`, `end_date_time` e um `tag` legível por humanos. O array `messages` usa os mesmos [campos de mensagem](#message-fields) que o endpoint de listagem.

---

## Editar, excluir e reagir a mensagens

Esses endpoints alteram uma mensagem após ela ter sido enviada. Dois deles alcançam o canal do contato, bem como sua própria cópia, portanto, leia a introdução da seção antes de configurá-los — o que é possível depende inteiramente do canal em que a conversa está ocorrendo.

**O que cada canal permite**

| Ação | Canais que podem alterar a cópia do contato | Limite de tempo |
|---|---|---|
| Editar uma mensagem enviada | Widget de chat, WhatsApp Web, Telegram, LinkedIn | Nenhum no widget de chat, 15 minutos no WhatsApp Web, 48 horas no Telegram, 60 minutos no LinkedIn |
| Excluir para todos | Widget de chat, WhatsApp Web, Telegram, LinkedIn | 60 minutos no LinkedIn; os outros não possuem limite publicado |
| Reagir com um emoji | WhatsApp Web, Telegram | Nenhum |

Em todos os outros canais — a API do WhatsApp Business, SMS, Instagram, Messenger, e-mail, LINE, canais personalizados — uma exclusão ainda remove a mensagem da sua caixa de entrada, mas o contato mantém a cópia dele, e editar ou reagir não é possível de forma alguma.

### Editar uma mensagem

`POST /contacts/{contactId}/messages/{messageId}/edit`

Reescreve uma mensagem que você já enviou, no dispositivo do contato e na sua cópia.

| Campo | Obrigatório | Descrição |
|---|---|---|
| `body` | Sim | O novo texto da mensagem. Não deve estar vazio e pode ter no máximo 4096 caracteres. |

Ao contrário da exclusão, isso **falha de forma explícita** quando o canal recusa: você recebe um `409` e sua cópia é deixada exatamente como a do contato, porque mostrar uma edição que eles nunca receberam deixaria os dois lados desalinhados. O campo `edit_reason` informa o motivo — a janela de edição do canal expirou, o canal está desconectado ou algo deu errado.

**cURL**

```bash
curl -X POST "https://api.youraiconnector.com/v1/contacts/contact123/messages/msg_1/edit" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "body": "Sorry - I meant Thursday at 3pm." }'
```

**JavaScript**

```javascript
const res = await fetch(
  "https://api.youraiconnector.com/v1/contacts/contact123/messages/msg_1/edit",
  {
    method: "POST",
    headers: {
      "X-API-Key": "YOUR_API_KEY",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ body: "Sorry - I meant Thursday at 3pm." }),
  }
);
const data = await res.json();
console.log(data.edited, data.edit_reason);
```

**Python**

```python
import requests

res = requests.post(
    "https://api.youraiconnector.com/v1/contacts/contact123/messages/msg_1/edit",
    headers={
        "X-API-Key": "YOUR_API_KEY",
        "Content-Type": "application/json",
    },
    json={"body": "Sorry - I meant Thursday at 3pm."},
)
data = res.json()
print(data.get("edited"), data.get("edit_reason"))
```

**Resposta** (`200 OK`):

```json
{
  "success": true,
  "contact_id": "contact123",
  "message_id": "msg_1",
  "edited": true,
  "edit_reason": "edit_dispatched"
}
```

Se o canal não aceitar a edição, você recebe um `409` em vez disso, e nada foi alterado:

```json
{
  "success": false,
  "error": "The message could not be edited",
  "edit_reason": "channel_disconnected"
}
```

Uma mensagem que já foi excluída, um canal que não permite edição de forma alguma e uma mensagem que é muito antiga para seu canal retornam `400` — a solicitação nunca chega ao canal.

### Excluir uma mensagem

`DELETE /contacts/{contactId}/messages/{messageId}`

Remove a mensagem da sua conversa e, onde o canal permite, retira a cópia do contato também. Sem corpo de solicitação.

Isso sempre responde `200` quando a mensagem existia, mesmo que a cópia do contato não pudesse ser retraída — sua cópia **foi** removida, então um erro seria enganoso. Leia os três campos na resposta para informar ao usuário o que realmente aconteceu:

| Campo | Descrição |
|---|---|
| `revoke_supported` | Se este canal pode retrair mensagens. |
| `revoked` | Se a cópia no dispositivo do contato foi removida. |
| `revoke_reason` | Por que não foi removida, quando `revoked` é `false` — por exemplo `revoke_window_closed` ou `already_deleted`. |

**cURL**

```bash
curl -X DELETE "https://api.youraiconnector.com/v1/contacts/contact123/messages/msg_1" \
  -H "X-API-Key: YOUR_API_KEY"
```

**JavaScript**

```javascript
const res = await fetch(
  "https://api.youraiconnector.com/v1/contacts/contact123/messages/msg_1",
  { method: "DELETE", headers: { "X-API-Key": "YOUR_API_KEY" } }
);
const data = await res.json();
console.log(data.revoked, data.revoke_reason);
```

**Python**

```python
import requests

res = requests.delete(
    "https://api.youraiconnector.com/v1/contacts/contact123/messages/msg_1",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
data = res.json()
print(data["revoked"], data["revoke_reason"])
```

**Resposta** (`200 OK`):

```json
{
  "success": true,
  "contact_id": "contact123",
  "message_id": "msg_1",
  "revoke_supported": true,
  "revoked": true,
  "revoke_reason": "revoke_dispatched"
}
```

> As mensagens excluídas não são removidas do histórico da conversa. Elas permanecem em `GET /contacts/{contactId}/messages` com `is_deleted: true` e um `body` e `media_url` vazios.

### Excluir várias mensagens de uma vez

`POST /contacts/{contactId}/messages/bulk-delete`

Limpa um lote de mensagens apenas do seu lado. Os corpos e anexos são esvaziados, mas **nada é retraído no dispositivo do contato** — para também retirar uma mensagem, exclua-a uma por uma com o endpoint de mensagem única acima.

| Campo | Obrigatório | Descrição |
|---|---|---|
| `message_ids` | Sim | Uma matriz não vazia de IDs de mensagem, até 500 por solicitação. `messageIds` é aceito como um alias. |

**cURL**

```bash
curl -X POST "https://api.youraiconnector.com/v1/contacts/contact123/messages/bulk-delete" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "message_ids": ["msg_1", "msg_2"] }'
```

**JavaScript**

```javascript
const res = await fetch(
  "https://api.youraiconnector.com/v1/contacts/contact123/messages/bulk-delete",
  {
    method: "POST",
    headers: {
      "X-API-Key": "YOUR_API_KEY",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ message_ids: ["msg_1", "msg_2"] }),
  }
);
console.log((await res.json()).deleted);
```

**Python**

```python
import requests

res = requests.post(
    "https://api.youraiconnector.com/v1/contacts/contact123/messages/bulk-delete",
    headers={
        "X-API-Key": "YOUR_API_KEY",
        "Content-Type": "application/json",
    },
    json={"message_ids": ["msg_1", "msg_2"]},
)
print(res.json()["deleted"])
```

**Resposta** (`200 OK`):

```json
{
  "success": true,
  "contact_id": "contact123",
  "deleted": 2
}
```

### Reagir a uma mensagem

`POST /contacts/{contactId}/messages/{messageId}/react`

Coloca sua própria reação de emoji em uma mensagem, ou a retira enviando uma string vazia. As reações do próprio contato nunca são alteradas.

| Campo | Obrigatório | Descrição |
|---|---|---|
| `emoji` | Sim | O emoji para reagir, ou `""` para remover sua reação. Deve ser uma única string sem espaços, com no máximo 16 caracteres. |

Assim como a edição, isso falha em vez de mostrar uma reação que o contato nunca recebeu, e a falha informa se vale a pena tentar novamente:

- `422` — nunca poderá ser entregue nesta conversa: o canal não suporta reações, a mensagem não tem um ID do lado do canal ou o emoji está fora do conjunto permitido pelo canal.
- `409` — o canal estava momentaneamente inacessível. Uma nova tentativa pode funcionar.

**cURL**

```bash
curl -X POST "https://api.youraiconnector.com/v1/contacts/contact123/messages/msg_1/react" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "emoji": "👍" }'
```

**JavaScript**

```javascript
const res = await fetch(
  "https://api.youraiconnector.com/v1/contacts/contact123/messages/msg_1/react",
  {
    method: "POST",
    headers: {
      "X-API-Key": "YOUR_API_KEY",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ emoji: "👍" }),
  }
);
const data = await res.json();
console.log(data.reactions);
```

**Python**

```python
import requests

res = requests.post(
    "https://api.youraiconnector.com/v1/contacts/contact123/messages/msg_1/react",
    headers={
        "X-API-Key": "YOUR_API_KEY",
        "Content-Type": "application/json",
    },
    json={"emoji": "👍"},
)
print(res.json()["reactions"])
```

**Resposta** (`200 OK`):

```json
{
  "success": true,
  "contact_id": "contact123",
  "message_id": "msg_1",
  "reaction_supported": true,
  "reaction_reason": "reaction_dispatched",
  "reactions": [
    {
      "emoji": "👍",
      "from_phone_number": "+31612345678",
      "from_me": true,
      "reacted_at": "2026-06-01T10:05:00.000Z"
    }
  ]
}
```

A matriz `reactions` é o conjunto completo de reações agora na mensagem, as suas e as do contato. Em um `409` ou `422`, ela é retornada inalterada, portanto, um cliente que renderiza diretamente a partir dela nunca mostra uma reação que não foi entregue.

### Avaliar ou marcar uma mensagem com estrela

`PATCH /contacts/{contactId}/messages/{messageId}`

Avalia uma mensagem com polegar para cima ou para baixo e/ou marca-a como importante. Isso é um controle apenas do seu lado — nada é enviado ao contato.

| Campo | Obrigatório | Descrição |
|---|---|---|
| `score` | Não | `1` curtir, `-1` descurtir, `0` limpa a avaliação. |
| `is_important` | Não | `true` marca a mensagem com estrela, `false` remove a estrela. Deve ser um booleano real, não a string `"true"`. |

Envie pelo menos um dos dois, ou você receberá um `400`. Apenas o que você envia é gravado, portanto, marcar uma mensagem com estrela nunca limpa sua avaliação e vice-versa — e a resposta ecoa apenas os campos que você enviou.

**cURL**

```bash
curl -X PATCH "https://api.youraiconnector.com/v1/contacts/contact123/messages/msg_1" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "score": 1, "is_important": true }'
```

**JavaScript**

```javascript
await fetch("https://api.youraiconnector.com/v1/contacts/contact123/messages/msg_1", {
  method: "PATCH",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ score: 1, is_important: true }),
});
```

**Python**

```python
import requests

requests.patch(
    "https://api.youraiconnector.com/v1/contacts/contact123/messages/msg_1",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={"score": 1, "is_important": True},
)
```

**Resposta** (`200 OK`):

```json
{
  "success": true,
  "contact_id": "contact123",
  "message_id": "msg_1",
  "score": 1,
  "is_important": true
}
```

---

## Marcar mensagens como lidas

Você pode limpar o status de não lido para mensagens específicas ou para toda a conversa.

### Marcar mensagens específicas como lidas

`POST /contacts/{contactId}/messages/mark-read`

Passe os IDs das mensagens a serem marcadas como lidas.

| Campo | Obrigatório | Descrição |
|---|---|---|
| `message_ids` | Sim | Um array não vazio de IDs de mensagem (até 500 por solicitação). |

**cURL**

```bash
curl -X POST "https://api.youraiconnector.com/v1/contacts/contact123/messages/mark-read" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "message_ids": ["aB3dE5fG7hI9jK1lM2nO", "cD4eF6gH8iJ0kL2mN3oP"]
  }'
```

**JavaScript**

```javascript
const res = await fetch(
  "https://api.youraiconnector.com/v1/contacts/contact123/messages/mark-read",
  {
    method: "POST",
    headers: {
      "X-API-Key": "YOUR_API_KEY",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      message_ids: ["aB3dE5fG7hI9jK1lM2nO", "cD4eF6gH8iJ0kL2mN3oP"],
    }),
  }
);
const data = await res.json();
console.log(data.marked_read);
```

**Python**

```python
import requests

res = requests.post(
    "https://api.youraiconnector.com/v1/contacts/contact123/messages/mark-read",
    headers={
        "X-API-Key": "YOUR_API_KEY",
        "Content-Type": "application/json",
    },
    json={"message_ids": ["aB3dE5fG7hI9jK1lM2nO", "cD4eF6gH8iJ0kL2mN3oP"]},
)
print(res.json()["marked_read"])
```

**Resposta** (`200 OK`):

```json
{
  "success": true,
  "contact_id": "contact123",
  "marked_read": 2
}
```

### Marcar todo o chat como lido

`POST /contacts/{contactId}/mark-read`

Limpa o selo de não lido de toda a conversa do contato na caixa de entrada. Nenhum corpo de solicitação é necessário.

**cURL**

```bash
curl -X POST "https://api.youraiconnector.com/v1/contacts/contact123/mark-read" \
  -H "X-API-Key: YOUR_API_KEY"
```

**JavaScript**

```javascript
const res = await fetch(
  "https://api.youraiconnector.com/v1/contacts/contact123/mark-read",
  { method: "POST", headers: { "X-API-Key": "YOUR_API_KEY" } }
);
const data = await res.json();
console.log(data.success);
```

**Python**

```python
import requests

res = requests.post(
    "https://api.youraiconnector.com/v1/contacts/contact123/mark-read",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
print(res.json()["success"])
```

**Resposta** (`200 OK`):

```json
{
  "success": true,
  "contact_id": "contact123"
}
```

### Marcar toda a conversa como não lida

`POST /contacts/{contactId}/mark-unread`

Coloca o selo de não lida de volta na conversa — útil quando alguém da sua equipe abriu um chat, mas está passando a tarefa adiante. Nenhum corpo de requisição é necessário.

Esta é uma flag exclusiva da caixa de entrada: ela **não** altera quando a conversa foi lida pela última vez, portanto, nenhum recibo de leitura é enviado ao contato em canais que oferecem suporte a eles.

**cURL**

```bash
curl -X POST "https://api.youraiconnector.com/v1/contacts/contact123/mark-unread" \
  -H "X-API-Key: YOUR_API_KEY"
```

**Resposta** (`200 OK`):

```json
{
  "success": true,
  "contact_id": "contact123"
}
```

---

## Exportar uma conversa

As exportações fornecem uma conversa inteira como uma transcrição legível, em vez de navegar pelas mensagens página por página. Cada endpoint de exportação aceita um `filter` de `all` (padrão), `text`, `media` ou `tool_use`, correspondendo ao filtro na lista de mensagens.

### Exportar o chat de um contato

`GET /chat-exports/{contactId}`

| Parâmetro de consulta | Obrigatório | Descrição |
|---|---|---|
| `format` | Não | `txt` (padrão) retorna um link de download para uma transcrição em texto simples. `json` retorna as mensagens como dados estruturados na resposta. |
| `filter` | Não | `all` (padrão), `text`, `media` ou `tool_use`. |

**cURL**

```bash
curl "https://api.youraiconnector.com/v1/chat-exports/contact123?format=json" \
  -H "X-API-Key: YOUR_API_KEY"
```

**Python**

```python
import requests

res = requests.get(
    "https://api.youraiconnector.com/v1/chat-exports/contact123",
    headers={"X-API-Key": "YOUR_API_KEY"},
    params={"format": "json"},
)
print(res.json()["data"]["messages"])
```

**Resposta com `format=json`** (`200 OK`):

```json
{
  "success": true,
  "data": {
    "contact": {
      "id": "contact123",
      "name": "Jane Doe",
      "phone": "+31612345678",
      "email": "jane@example.com"
    },
    "messages": [
      {
        "body": "Hi! I have a question about my order.",
        "direction": "inbound",
        "timestamp": "2026-06-01T09:55:00.000Z",
        "type": "text",
        "media_url": null,
        "media_content_type": null,
        "name": null,
        "args": null
      }
    ]
  }
}
```

Com `format=txt` (o padrão), `data` é, em vez disso, um link de download para o arquivo de transcrição gerado:

```json
{
  "success": true,
  "data": "https://storage.googleapis.com/.../chat-export-contact123-....txt"
}
```

> **O link de download tem curta duração.** Busque o arquivo assim que receber o link, em vez de armazená-lo — solicite uma nova exportação quando precisar da transcrição novamente.

### Exportar todas as conversas recentes

`GET /chat-exports/recent`

Exporta as conversas de todos os contatos que estiveram ativos nas últimas X horas, em uma única chamada.

| Parâmetro de consulta | Obrigatório | Descrição |
|---|---|---|
| `hours` | Sim | Quantas horas de atividade considerar. Deve ser um número inteiro positivo. |
| `format` | Não | `json` (padrão) retorna uma entrada por contato. `txt` retorna um único arquivo de texto para download com todas as conversas. |
| `limit` | Não | Número máximo de contatos para exportar. Padrão `50`, máximo `100`. |
| `filter` | Não | `all` (padrão), `text`, `media` ou `tool_use`. |

**cURL**

```bash
curl "https://api.youraiconnector.com/v1/chat-exports/recent?hours=24&limit=25" \
  -H "X-API-Key: YOUR_API_KEY"
```

**Resposta** (`200 OK`):

```json
{
  "success": true,
  "data": {
    "hours_ago": 24,
    "total_contacts": 2,
    "exports": [
      {
        "contactId": "contact123",
        "contactName": "Jane Doe",
        "phoneNumber": "+31612345678",
        "email": "jane@example.com",
        "messageCount": 12,
        "chatExport": "Acme Export - Jane Doe\nPhone: +31612345678\n..."
      }
    ]
  }
}
```

Com `format=txt`, a resposta é o próprio arquivo de texto, enviado como um download em vez de JSON.

> Esta chamada única extrai o histórico completo de cada contato correspondente, portanto, mantenha `hours` e `limit` moderados em contas com muito movimento.

### Enviar uma transcrição por e-mail para o contato

`POST /chat-exports/{contactId}/email`

Envia ao contato a sua própria transcrição da conversa por e-mail — o fluxo "enviar este chat por e-mail", acionado a partir do seu próprio sistema.

| Campo | Obrigatório | Descrição |
|---|---|---|
| `recipient_email` | Não | Para onde enviar. O padrão é o endereço de e-mail armazenado do contato. |
| `via` | Não | `auto` (padrão) escolhe a melhor rota, `transactional` envia como um e-mail do sistema, `email_channel` envia a partir do seu canal de e-mail conectado. |
| `note` | Não | Uma breve linha sua exibida acima da transcrição. Até 1000 caracteres. |

**cURL**

```bash
curl -X POST "https://api.youraiconnector.com/v1/chat-exports/contact123/email" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "note": "Here is a copy of our chat, as promised." }'
```

**Resposta** (`200 OK`):

```json
{
  "success": true,
  "data": {
    "via": "transactional",
    "recipientEmail": "jane@example.com",
    "messageCount": 42,
    "omittedCount": 0
  }
}
```

`omittedCount` informa quantas das mensagens mais antigas foram omitidas para manter o e-mail com um tamanho razoável. Um `200` significa que a transcrição foi criada e colocada na fila para envio, não que ela já chegou à caixa de entrada.

---

## Pausar ou retomar a IA para um contato

`PUT /contacts/{contactId}`

Defina `is_bot_active` como `false` para impedir que a IA responda a um contato, e volte para `true` para devolver a conversa. Este é o interruptor de controle que você deseja quando um humano assume uma conversa: as mensagens enviadas por você com a API ainda são entregues enquanto o bot está pausado.

**cURL**

```bash
curl -X PUT "https://api.youraiconnector.com/v1/contacts/contact123" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "is_bot_active": false }'
```

**JavaScript**

```javascript
await fetch("https://api.youraiconnector.com/v1/contacts/contact123", {
  method: "PUT",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ is_bot_active: false }),
});
```

**Python**

```python
import requests

requests.put(
    "https://api.youraiconnector.com/v1/contacts/contact123",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={"is_bot_active": False},
)
```

**Resposta**

```json
{
  "success": true,
  "contact_id": "contact123"
}
```

**Pausando como parte da resposta**

Se um humano estiver assumindo o atendimento ao enviar uma resposta, você pode pausar o bot na mesma solicitação em vez de fazer uma segunda chamada. `POST /contacts/{contactId}/send-message` aceita duas flags opcionais:

| Campo | Descrição |
|---|---|
| `pauseBot` | `true` pausa a IA para este contato conforme a mensagem é enviada. |
| `clearIncompleteReply` | `true` descarta uma resposta do bot inacabada para que ela não seja retomada posteriormente. |

```bash
curl -X POST "https://api.youraiconnector.com/v1/contacts/contact123/send-message" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "body": "Hi, Sarah here - taking over from the assistant.",
    "pauseBot": true,
    "clearIncompleteReply": true
  }'
```

A resposta inclui `"botPaused": true` quando a pausa foi aplicada.

> Marcar um contato como privado com [`POST /contacts/bulk-flag`](contacts.md) também pausa o bot para ele. Veja [Contatos](contacts.md) para a lista completa de campos.

---

## Construindo sua própria caixa de entrada

Tudo o que uma caixa de entrada precisa está nesta página e em [Contatos](contacts.md):

| O que você precisa | Endpoint |
|---|---|
| Listar conversas | `GET /contacts` |
| Ler uma conversa | `GET /contacts/{contactId}/messages` |
| Listar sessões de chat de um contato | `GET /chat-sessions/{contactId}` |
| Ver o que chegou recentemente | `GET /chat-sessions/recent` |
| Ler uma sessão de chat | `GET /contacts/{contactId}/chat-sessions/{sessionId}/messages` |
| Enviar uma resposta manual | `POST /contacts/{contactId}/send-message` |
| Corrigir uma resposta que você acabou de enviar | `POST /contacts/{contactId}/messages/{messageId}/edit` |
| Remover uma mensagem | `DELETE /contacts/{contactId}/messages/{messageId}` |
| Limpar várias mensagens | `POST /contacts/{contactId}/messages/bulk-delete` |
| Reagir com um emoji | `POST /contacts/{contactId}/messages/{messageId}/react` |
| Avaliar ou marcar uma mensagem com estrela | `PATCH /contacts/{contactId}/messages/{messageId}` |
| Marcar como lida | `POST /contacts/{contactId}/mark-read` |
| Devolver um chat para a equipe | `POST /contacts/{contactId}/mark-unread` |
| Exportar uma transcrição | `GET /chat-exports/{contactId}` |
| Pausar ou retomar a IA | `PUT /contacts/{contactId}` com `is_bot_active` |

Para atualizações em tempo real, inscreva-se nos eventos `New Message`, `Replies`, `Human Alerted` e `Chat Concluded` com [Webhooks](webhooks.md) em vez de consultar esta API periodicamente.

---

## Erros da API de Mensagens

Os endpoints de mensagem retornam o envelope de erro padrão:

```json
{
  "success": false,
  "error": "Contact not found"
}
```

| Status | Quando ocorre em um endpoint de mensagem |
|---|---|
| `400` | Um campo obrigatório está faltando ou um parâmetro é inválido (bad `limit`, `hours`, `filter`, `direction`, `status`, um array `message_ids` vazio ou com mais de 500 itens, um `cursor` inválido, uma edição `body` vazia ou longa demais, um `score` fora de `-1`/`0`/`1`, ou um emoji com espaços ou mais de 16 caracteres). Também retornado quando uma mensagem não pode ser editada de forma alguma — ela foi excluída, seu canal não permite edição ou está fora da janela de edição daquele canal. |
| `404` | O contato, a sessão de chat ou um dos IDs de mensagem fornecidos não foi encontrado. |
| `409` | O canal não aceita a alteração no momento. Nada foi gravado: em uma edição, `edit_reason` diz o motivo; em uma reação, o canal estava momentaneamente inacessível e uma nova tentativa pode funcionar. |
| `422` | O contato não pode receber mensagens de saída (não perturbe, privado ou um canal não suportado), ou uma reação nunca pode ser entregue nesta conversa (`reaction_reason` diz qual). |

Os códigos compartilhados que todo endpoint pode retornar — `401`, `403` (seu plano não inclui acesso à API), `429` (limite de taxa) e `500` — estão listados com orientações de nova tentativa em [Erros e Paginação](errors-and-pagination.md).

---

## Próximos passos

- [Webhooks](webhooks.md) — receba atualizações de status de entrega em vez de fazer polling.
- [Contatos](contacts.md) — crie e pesquise os contatos para os quais você envia mensagens.
- [Agendamentos](appointments.md) — reserve e gerencie agendamentos para seus contatos.
