
# Messaggi e conversazioni

Le API dei messaggi ti consentono di inviare un messaggio a qualsiasi contatto, rileggere una conversazione, correggere o rimuovere un messaggio già inviato, reagire a un messaggio, recuperare l'intero thread di una sessione di chat, esportare una trascrizione e contrassegnare le chat come lette o non lette, il tutto senza aprire la posta in arrivo.

Tutti i percorsi in questa pagina sono relativi all'URL di base `https://api.youraiconnector.com/v1`. Ogni richiesta richiede la tua chiave API: consulta [Autenticazione](authentication.md) per l'elenco completo delle modalità di invio. Gli esempi seguenti utilizzano l'intestazione `X-API-Key`, con un esempio cURL che mostra anche il formato di query `?apiKey=`.

> **Come funziona la consegna:** L'invio di un messaggio **non** attende che arrivi a destinazione. L'API accetta il messaggio, risponde immediatamente con un ID messaggio e quindi lo consegna in background sul canale del contatto (WhatsApp, SMS, Instagram, ecc.). Per monitorare se un messaggio è stato effettivamente consegnato o letto, ascolta gli aggiornamenti di stato con i [Webhook](webhooks.md): non eseguire il polling. La risposta di invio conferma solo che il messaggio è stato accettato.

---

## Inviare un messaggio

Esistono due modi per inviare un messaggio. Scegli quello più adatto al modo in cui identifichi già il contatto:

- **Invia tramite ID contatto** — conosci già l'ID del contatto (ad esempio, hai creato il contatto tramite l'API o l'hai ottenuto da un webhook). Usa `POST /contacts/{contactId}/send-message`.
- **Invia tramite identità del contatto** — conosci il numero di telefono del contatto, l'ID Instagram, ecc., ma non il suo ID interno. Usa `POST /contacts/send` e lascia che la piattaforma trovi il contatto corretto.

Entrambi mettono in coda il messaggio allo stesso modo e lo consegnano sul canale in cui si trova il contatto. Non devi scegliere un trasporto: la piattaforma instrada i contatti WhatsApp su WhatsApp, i contatti SMS su SMS e così via.

### Invia tramite ID contatto

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

| Campo | Obbligatorio | Descrizione |
|---|---|---|
| `body` | Sì | Il testo del messaggio da inviare. |
| `mediaUrl` | No | URL di un file multimediale (immagine, documento, ecc.) da allegare. |
| `mediaContentType` | No | Tipo MIME del file multimediale allegato, ad es. `image/jpeg`. |
| `pauseBot` | No | `true` sospende l'IA per questo contatto mentre il messaggio viene inviato — per un intervento umano. Vedi [Sospendere o riprendere l'IA](#pause-or-resume-the-ai-for-one-contact). |
| `clearIncompleteReply` | No | `true` scarta una risposta del bot a metà, in modo che non riprenda dopo il tuo messaggio. |

**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"])
```

**Risposta** (`200 OK`):

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

### Invia tramite identità del contatto

`POST /contacts/send`

Utilizza questo metodo quando non disponi dell'ID interno del contatto. Fornisci il `body` del messaggio più **o** un `contact_id`, **o** un `channel` insieme al campo di identità che corrisponde a quel canale.

| Campo | Obbligatorio | Descrizione |
|---|---|---|
| `body` | Sì | Il testo del messaggio da inviare. |
| `contact_id` | No | ID di un contatto esistente. Quando impostato, i campi di identità sottostanti non sono necessari. |
| `channel` | No | Canale su cui inviare. Obbligatorio quando `contact_id` non è fornito. Uno dei 14 canali di invio in uscita: `whatsapp`, `whatsapp_web`, `sms`, `instagram`, `instagram_private`, `messenger`, `telegram`, `chat-widget`, `custom`, `email`, `line`, `imessage`, `linkedin`, `viber`. |
| `phone_number` | No | Numero di telefono del contatto in formato internazionale. Utilizzato con `whatsapp`, `whatsapp_web` e `sms`. |
| `instagram_id` | No | ID utente Instagram del contatto. Utilizzato con `instagram`. |
| `messenger_id` | No | ID utente Messenger del contatto. Utilizzato con `messenger`. |
| `telegram_user_id` | No | ID utente Telegram del contatto. Utilizzato con `telegram`. |
| `media_url` | No | URL di un file multimediale da allegare. |
| `media_content_type` | No | Tipo MIME del file multimediale allegato, ad es. `image/jpeg`. |

**Quali canali possono essere risolti tramite identità.** Solo sei dei 14 accettano un campo di identità invece di un `contact_id`: `whatsapp`, `whatsapp_web` e `sms` vengono cercati tramite `phone_number`, `instagram` tramite `instagram_id`, `messenger` tramite `messenger_id` e `telegram` tramite `telegram_user_id`. Gli altri otto — `instagram_private`, `chat-widget`, `custom`, `email`, `line`, `imessage`, `linkedin` e `viber` — non hanno un'identità pubblica da ricercare, quindi l'invio su tali canali richiede `contact_id`; passare solo `channel` restituisce un `400` che indica che `contact_id` è richiesto.

**cURL** (utilizzando il formato di query `?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"])
```

**Risposta** (`201 Created`):

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

> **Perché un messaggio potrebbe essere rifiutato:** Un contatto con la modalità non disturbare o privata attivata non può ricevere messaggi in uscita — la richiesta fallisce con un `422`. Se nessun contatto corrisponde all'ID o all'identità fornita, si ottiene un `404`.

---

## Elenca i messaggi di un contatto

`GET /contacts/{contactId}/messages`

Restituisce i messaggi di un contatto, dal più recente, con paginazione basata su cursore.

| Parametro di query | Obbligatorio | Descrizione |
|---|---|---|
| `limit` | No | Dimensione della pagina. Predefinito `50`, massimo `100`. |
| `cursor` | No | Il valore `next_cursor` da una risposta precedente. Restituisce i messaggi precedenti al cursore. |
| `filter` | No | Filtra per tipo di contenuto: `all` (predefinito), `text`, `media` o `tool_use`. |
| `direction` | No | Filtra per direzione: `all` (predefinito), `inbound` (ricevuto dal contatto) o `outbound` (inviato da te). |

> **Nota sul filtraggio e la paginazione:** I filtri `filter` e `direction` vengono applicati a ogni pagina dopo la lettura, quindi una pagina filtrata può contenere meno elementi di `limit`. Il `next_cursor` avanza comunque attraverso l'intera conversazione, quindi continua la paginazione finché `next_cursor` non è `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"])
```

**Risposta** (`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"
}
```

### Campi del messaggio

| Campo | Descrizione |
|---|---|
| `id` | ID univoco del messaggio. |
| `body` | Contenuto testuale del messaggio. |
| `direction` | `inbound` (ricevuto dal contatto) o `outbound` (inviato dal tuo account). |
| `channel` | Canale su cui il messaggio è stato inviato o ricevuto (es. `whatsapp`, `sms`, `instagram`). |
| `status` | Stato di consegna corrente, es. `Created`, `sent`, `delivered`, `read`, `failed`. |
| `type` | Tipo di messaggio. I messaggi di solo testo hanno un tipo `null`; l'attività dello strumento di assistenza automatizzata è contrassegnata come `tool_use`. |
| `timestamp` | Data e ora ISO 8601 di creazione del messaggio. |
| `media_url` | URL di un file multimediale allegato, se presente. |
| `media_content_type` | Tipo MIME del file multimediale allegato, se presente. |
| `bot_reply` | `true` quando il messaggio è stato generato dall'assistente AI. |
| `score` | La tua valutazione del messaggio: `1` pollice in su, `-1` pollice in giù, `0` quando non è stato valutato. Vedi [Valuta o aggiungi ai preferiti un messaggio](#rate-or-star-a-message). |
| `is_important` | `true` quando il messaggio è stato aggiunto ai preferiti. |
| `is_deleted` | `true` quando il messaggio è stato eliminato. I messaggi eliminati rimangono nell'elenco ma i loro campi `body` e `media_url` sono vuoti. |
| `reactions` | Reazioni emoji al messaggio, da entrambe le parti. Sempre un array: vuoto quando non ce ne sono. Ogni voce ha `emoji`, `from_phone_number`, `from_me` (`true` quando la reazione è la tua) e `reacted_at`. |

---

## Elenca sessioni di chat

Una sessione di chat è una finestra di conversazione con un contatto: si apre quando inizia a parlare e si chiude quando la conversazione è conclusa. Le sessioni sono il modo in cui puoi suddividere una lunga cronologia in conversazioni leggibili invece di un elenco infinito.

### Sessioni recenti per tutti i contatti

`GET /chat-sessions/recent`

Restituisce le sessioni iniziate nelle ultime X ore, dalla più recente, per ogni contatto sull'account.

| Parametro di query | Obbligatorio | Descrizione |
|---|---|---|
| `hours` | Sì | Quante ore indietro controllare. Deve essere un numero intero positivo. |
| `status` | No | Restituisci solo le sessioni con questo stato: `ChatSessionOpened` o `ChatSessionClosed`. |
| `limit` | No | Numero massimo di sessioni da restituire. Predefinito `100`, massimo `100`. |
| `includeMessages` | No | `true` aggiunge un array `messages` a ogni sessione. Disattivato per impostazione predefinita perché rende la risposta molto più grande. |

**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"])
```

**Risposta** (`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"
      }
    ]
  }
}
```

### Tutte le sessioni per un singolo contatto

`GET /chat-sessions/{contactId}`

Restituisce ogni sessione di chat per un singolo contatto. Stessi parametri `status`, `limit` e `includeMessages` di cui sopra: `hours` non si applica qui.

**cURL**

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

**Risposta** (`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"
      }
    ]
  }
}
```

> **I nomi dei campi ID sessione differiscono tra i due endpoint.** L'elenco delle sessioni recenti lo chiama `session_id` (contiene anche i dettagli del contatto, poiché le sessioni provengono da molti contatti); l'elenco per contatto lo chiama `id`. Entrambi i valori sono ciò che passi come `{sessionId}` quando recuperi l'intero thread qui sotto.

Quando `includeMessages=true`, ogni sessione ottiene un array `messages` le cui voci contengono `id`, `body`, `direction`, `timestamp`, `type`, `channel` e `status`.

---

## Recupera un thread di sessione chat

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

Una sessione di chat raggruppa i messaggi di un contatto in un'unica finestra di conversazione. Questo endpoint restituisce l'intero thread di una singola sessione, **dal più vecchio al più recente**, insieme ai metadati della sessione. È possibile trovare gli ID delle sessioni per un contatto tramite gli endpoint delle sessioni di 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"])
```

**Risposta** (`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
    }
  ]
}
```

L'oggetto `session` riporta `status` (`ChatSessionOpened` se attiva, `ChatSessionClosed` una volta terminata), `start_date_time`, `end_date_time` e un `tag` leggibile dall'utente. L'array `messages` utilizza gli stessi [campi del messaggio](#message-fields) dell'endpoint di elenco.

---

## Modifica, elimina e reagisci ai messaggi

Questi endpoint modificano un messaggio dopo che è stato inviato. Due di essi raggiungono il canale del contatto oltre alla tua copia, quindi leggi l'introduzione della sezione prima di implementarli: ciò che è possibile dipende interamente dal canale su cui si trova la conversazione.

**Cosa consente ogni canale**

| Azione | Canali che possono modificare la copia del contatto | Limite di tempo |
|---|---|---|
| Modifica di un messaggio inviato | Chat widget, WhatsApp Web, Telegram, LinkedIn | Nessuno sul chat widget, 15 minuti su WhatsApp Web, 48 ore su Telegram, 60 minuti su LinkedIn |
| Elimina per tutti | Chat widget, WhatsApp Web, Telegram, LinkedIn | 60 minuti su LinkedIn; gli altri non hanno un limite pubblicato |
| Reagisci con un'emoji | WhatsApp Web, Telegram | Nessuno |

Su ogni altro canale — WhatsApp Business API, SMS, Instagram, Messenger, email, LINE, canali personalizzati — un'eliminazione rimuove comunque il messaggio dalla tua casella di posta, ma il contatto mantiene la propria copia, e la modifica o la reazione non sono affatto possibili.

### Modifica un messaggio

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

Riscrive un messaggio che hai già inviato, sul dispositivo del contatto e nella tua copia.

| Campo | Obbligatorio | Descrizione |
|---|---|---|
| `body` | Sì | Il nuovo testo del messaggio. Non deve essere vuoto e può contenere al massimo 4096 caratteri. |

A differenza dell'eliminazione, questa operazione **fallisce in modo esplicito** quando il canale rifiuta: ricevi un `409` e la tua copia viene lasciata esattamente come quella del contatto, perché mostrare una modifica che non hanno mai ricevuto metterebbe le due parti fuori sincronia. Il campo `edit_reason` ti dice il perché — la finestra di modifica del canale è chiusa, il canale è disconnesso o qualcos'altro è andato storto.

**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"))
```

**Risposta** (`200 OK`):

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

Se il canale non accetta la modifica, ricevi invece un `409` e nulla viene modificato:

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

Un messaggio che è già stato eliminato, un canale che non può affatto modificare e un messaggio troppo vecchio per il suo canale restituiscono tutti `400` — la richiesta non raggiunge mai il canale.

### Elimina un messaggio

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

Rimuove il messaggio dalla tua conversazione e, dove il canale lo consente, ritira anche la copia del contatto. Nessun corpo della richiesta.

Questa operazione risponde sempre `200` quando il messaggio esisteva, anche se la copia del contatto non poteva essere ritirata — la tua copia **è** sparita, quindi un errore sarebbe fuorviante. Leggi i tre campi nella risposta per comunicare all'utente cosa è successo realmente:

| Campo | Descrizione |
|---|---|
| `revoke_supported` | Se questo canale può ritirare i messaggi. |
| `revoked` | Se la copia sul dispositivo del contatto è stata rimossa. |
| `revoke_reason` | Perché non è stata rimossa, quando `revoked` è `false` — ad esempio `revoke_window_closed` o `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"])
```

**Risposta** (`200 OK`):

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

> I messaggi eliminati non vengono rimossi dalla cronologia della conversazione. Rimangono in `GET /contacts/{contactId}/messages` con `is_deleted: true` e un `body` vuoto e `media_url`.

### Eliminare più messaggi contemporaneamente

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

Cancella un gruppo di messaggi solo dal tuo lato. I corpi e gli allegati vengono svuotati, ma **nulla viene ritirato sul dispositivo del contatto**: per ritirare anche un messaggio, eliminalo uno alla volta con l'endpoint per singolo messaggio qui sopra.

| Campo | Obbligatorio | Descrizione |
|---|---|---|
| `message_ids` | Sì | Un array non vuoto di ID messaggio, fino a 500 per richiesta. `messageIds` è accettato come 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"])
```

**Risposta** (`200 OK`):

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

### Reagire a un messaggio

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

Inserisce la tua reazione emoji su un messaggio, o la rimuove inviando una stringa vuota. Le reazioni del contatto non vengono mai toccate.

| Campo | Obbligatorio | Descrizione |
|---|---|---|
| `emoji` | Sì | L'emoji con cui reagire, o `""` per rimuovere la tua reazione. Deve essere una singola stringa senza spazi, di massimo 16 caratteri. |

Come per la modifica, questa operazione fallisce invece di mostrare una reazione che il contatto non ha mai ricevuto, e l'errore ti indica se vale la pena riprovare:

- `422` — non può mai essere consegnata in questa conversazione: il canale non supporta le reazioni, il messaggio non ha un ID lato canale, o l'emoji è al di fuori del set consentito dal canale.
- `409` — il canale era momentaneamente irraggiungibile. Un nuovo tentativo potrebbe funzionare.

**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"])
```

**Risposta** (`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"
    }
  ]
}
```

L'array `reactions` è l'insieme completo delle reazioni presenti sul messaggio, le tue e quelle del contatto. In caso di `409` o `422` viene restituito invariato, quindi un client che esegue il rendering direttamente da esso non mostrerà mai una reazione che non è stata consegnata.

### Valutare o aggiungere un messaggio ai preferiti

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

Valuta un messaggio con pollice in su o in giù e/o lo contrassegna come importante. Si tratta di una gestione contabile solo dal tuo lato: nulla viene inviato al contatto.

| Campo | Obbligatorio | Descrizione |
|---|---|---|
| `score` | No | `1` pollice in su, `-1` pollice in giù, `0` rimuove la valutazione. |
| `is_important` | No | `true` aggiunge una stella al messaggio, `false` la rimuove. Deve essere un valore booleano reale, non la stringa `"true"`. |

Invia almeno uno dei due, altrimenti riceverai un `400`. Viene scritto solo ciò che invii, quindi aggiungere una stella a un messaggio non rimuove mai la sua valutazione e viceversa — e la risposta riporta solo i campi che hai inviato.

**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},
)
```

**Risposta** (`200 OK`):

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

---

## Contrassegna i messaggi come letti

È possibile rimuovere lo stato di non letto per messaggi specifici o per l'intera conversazione.

### Contrassegna messaggi specifici come letti

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

Passa gli ID dei messaggi da contrassegnare come letti.

| Campo | Obbligatorio | Descrizione |
|---|---|---|
| `message_ids` | Sì | Un array non vuoto di ID messaggio (fino a 500 per richiesta). |

**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"])
```

**Risposta** (`200 OK`):

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

### Contrassegna l'intera chat come letta

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

Rimuove l'indicatore di non letto per l'intera conversazione del contatto nella posta in arrivo. Non è richiesto alcun corpo della richiesta.

**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"])
```

**Risposta** (`200 OK`):

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

### Contrassegna l'intera chat come non letta

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

Ripristina l'indicatore di non letto sulla conversazione: utile quando qualcuno del tuo team ha aperto una chat ma la sta passando a qualcun altro. Non è richiesto alcun corpo della richiesta.

Questo è un flag valido solo per la posta in arrivo: **non** modifica l'orario dell'ultima lettura della conversazione, quindi non viene inviata alcuna conferma di lettura al contatto sui canali che le supportano.

**cURL**

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

**Risposta** (`200 OK`):

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

---

## Esporta una conversazione

Le esportazioni forniscono un'intera conversazione come trascrizione leggibile, invece di sfogliare i messaggi pagina per pagina. Ogni endpoint di esportazione accetta un `filter` di `all` (predefinito), `text`, `media` o `tool_use`, che corrisponde al filtro nell'elenco dei messaggi.

### Esporta la chat di un contatto

`GET /chat-exports/{contactId}`

| Parametro di query | Obbligatorio | Descrizione |
|---|---|---|
| `format` | No | `txt` (predefinito) restituisce un link per il download di una trascrizione in testo semplice. `json` restituisce i messaggi come dati strutturati nella risposta. |
| `filter` | No | `all` (predefinito), `text`, `media` o `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"])
```

**Risposta con `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
      }
    ]
  }
}
```

Con `format=txt` (il predefinito), `data` è invece un link per il download del file di trascrizione generato:

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

> **Il link per il download ha una durata limitata.** Recupera il file non appena ricevi il link invece di memorizzarlo: richiedi una nuova esportazione quando hai di nuovo bisogno della trascrizione.

### Esporta ogni conversazione recente

`GET /chat-exports/recent`

Esporta le conversazioni di tutti i contatti che sono stati attivi nelle ultime X ore, in un'unica chiamata.

| Parametro di query | Obbligatorio | Descrizione |
|---|---|---|
| `hours` | Sì | Quante ore di attività considerare a ritroso. Deve essere un numero intero positivo. |
| `format` | No | `json` (predefinito) restituisce una voce per contatto. `txt` restituisce un singolo file di testo scaricabile contenente ogni conversazione. |
| `limit` | No | Numero massimo di contatti da esportare. Predefinito `50`, massimo `100`. |
| `filter` | No | `all` (predefinito), `text`, `media` o `tool_use`. |

**cURL**

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

**Risposta** (`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..."
      }
    ]
  }
}
```

Con `format=txt` la risposta è il file di testo stesso, inviato come download anziché come JSON.

> Questa singola chiamata estrae l'intera cronologia di ogni contatto corrispondente, quindi mantieni `hours` e `limit` moderati su account molto attivi.

### Invia una trascrizione via email al contatto

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

Invia al contatto la propria trascrizione della conversazione via email: il flusso "inviami questa chat via email", gestito dal tuo sistema.

| Campo | Obbligatorio | Descrizione |
|---|---|---|
| `recipient_email` | No | Dove inviarla. Per impostazione predefinita utilizza l'indirizzo email salvato del contatto. |
| `via` | No | `auto` (predefinito) sceglie il percorso migliore, `transactional` la invia come email di sistema, `email_channel` la invia dal tuo canale email collegato. |
| `note` | No | Una breve riga da parte tua mostrata sopra la trascrizione. Fino a 1000 caratteri. |

**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." }'
```

**Risposta** (`200 OK`):

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

`omittedCount` ti indica quanti dei messaggi più vecchi sono stati esclusi per mantenere l'email di una lunghezza ragionevole. Un `200` significa che la trascrizione è stata creata e messa in coda per l'invio, non che sia già arrivata nella casella di posta.

---

## Sospendere o riprendere l'IA per un singolo contatto

`PUT /contacts/{contactId}`

Imposta `is_bot_active` su `false` per impedire all'IA di rispondere a un contatto, e riportalo su `true` per restituire la conversazione. Questo è l'interruttore di controllo che ti serve quando un essere umano interviene in una conversazione: i messaggi in uscita inviati tramite API vengono comunque consegnati mentre il bot è in pausa.

**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},
)
```

**Risposta**

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

**Sospensione durante la risposta**

Se un essere umano sta intervenendo inviando una risposta, puoi sospendere il bot nella stessa richiesta invece di effettuare una seconda chiamata. `POST /contacts/{contactId}/send-message` accetta due flag opzionali:

| Campo | Descrizione |
|---|---|
| `pauseBot` | `true` sospende l'IA per questo contatto mentre il messaggio viene inviato. |
| `clearIncompleteReply` | `true` scarta una risposta del bot a metà, in modo che non riprenda in seguito. |

```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
  }'
```

La risposta include `"botPaused": true` quando la pausa è stata applicata.

> Contrassegnare un contatto come privato con [`POST /contacts/bulk-flag`](contacts.md) sospende anche il bot per quel contatto. Vedi [Contatti](contacts.md) per l'elenco completo dei campi.

---

## Creare la propria casella di posta

Tutto ciò di cui una casella di posta ha bisogno si trova in questa pagina e in [Contatti](contacts.md):

| Cosa ti serve | Endpoint |
|---|---|
| Elenca conversazioni | `GET /contacts` |
| Leggi una conversazione | `GET /contacts/{contactId}/messages` |
| Elenca le sessioni di chat di un contatto | `GET /chat-sessions/{contactId}` |
| Vedi cosa è arrivato di recente | `GET /chat-sessions/recent` |
| Leggi una sessione di chat | `GET /contacts/{contactId}/chat-sessions/{sessionId}/messages` |
| Invia una risposta manuale | `POST /contacts/{contactId}/send-message` |
| Correggi una risposta appena inviata | `POST /contacts/{contactId}/messages/{messageId}/edit` |
| Rimuovi un messaggio | `DELETE /contacts/{contactId}/messages/{messageId}` |
| Elimina diversi messaggi | `POST /contacts/{contactId}/messages/bulk-delete` |
| Reagisci con un'emoji | `POST /contacts/{contactId}/messages/{messageId}/react` |
| Valuta o aggiungi una stella a un messaggio | `PATCH /contacts/{contactId}/messages/{messageId}` |
| Segna come letto | `POST /contacts/{contactId}/mark-read` |
| Restituisci una chat al team | `POST /contacts/{contactId}/mark-unread` |
| Esporta una trascrizione | `GET /chat-exports/{contactId}` |
| Metti in pausa o riprendi l'IA | `PUT /contacts/{contactId}` con `is_bot_active` |

Per aggiornamenti in tempo reale, iscriviti agli eventi `New Message`, `Replies`, `Human Alerted` e `Chat Concluded` con i [Webhook](webhooks.md) invece di interrogare questa API a intervalli regolari.

---

## Errori dell'API Messaggi

Gli endpoint dei messaggi restituiscono il pacchetto di errore standard:

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

| Stato | Quando si verifica su un endpoint di messaggio |
|---|---|
| `400` | Manca un campo obbligatorio o un parametro non è valido (`limit`, `hours`, `filter`, `direction`, `status` errati, un array `message_ids` vuoto o superiore a 500, un `cursor` non valido, una modifica `body` vuota o troppo lunga, un `score` al di fuori di `-1`/`0`/`1`, o un'emoji con spazi o superiore a 16 caratteri). Restituito anche quando un messaggio non può essere modificato affatto: è stato eliminato, il suo canale non supporta le modifiche o è oltre la finestra di modifica di quel canale. |
| `404` | Il contatto, la sessione di chat o uno degli ID messaggio forniti non è stato trovato. |
| `409` | Il canale non accetta la modifica in questo momento. Non è stato scritto nulla: in caso di modifica, `edit_reason` spiega il perché; in caso di reazione, il canale era momentaneamente irraggiungibile e un nuovo tentativo potrebbe funzionare. |
| `422` | Il contatto non può ricevere messaggi in uscita (non disturbare, privato o un canale non supportato), oppure una reazione non può mai essere consegnata in questa conversazione (`reaction_reason` indica quale). |

I codici condivisi che ogni endpoint può restituire — `401`, `403` (il tuo piano non include l'accesso all'API), `429` (limite di frequenza) e `500` — sono elencati con indicazioni sui tentativi in [Errori e Paginazione](errors-and-pagination.md).

---

## Passaggi successivi

- [Webhooks](webhooks.md) — ricevi aggiornamenti sullo stato di consegna invece di eseguire il polling.
- [Contatti](contacts.md) — crea e cerca i contatti a cui invii messaggi.
- [Appuntamenti](appointments.md) — prenota e gestisci gli appuntamenti per i tuoi contatti.
