
# Berichten & Gesprekken

Met de Messages API kun je een bericht naar elke contactpersoon sturen, een conversatie teruglezen, een reeds verzonden bericht corrigeren of verwijderen, op een bericht reageren, een volledige chat-sessie ophalen, een transcript exporteren en chats als gelezen of ongelezen markeren — en dat alles zonder de inbox te openen.

Alle paden op deze pagina zijn relatief ten opzichte van de basis-URL `https://api.youraiconnector.com/v1`. Voor elk verzoek is je API-sleutel vereist — zie [Authenticatie](authentication.md) voor de volledige lijst met manieren om deze te verzenden. De onderstaande voorbeelden gebruiken de `X-API-Key`-header, waarbij één cURL-voorbeeld ook de `?apiKey=`-queryvorm laat zien.

> **Hoe aflevering werkt:** Het verzenden van een bericht wacht **niet** tot het is aangekomen. De API accepteert je bericht, reageert onmiddellijk met een bericht-ID en levert het vervolgens op de achtergrond af via het kanaal van de contactpersoon (WhatsApp, SMS, Instagram, enzovoort). Om bij te houden of een bericht daadwerkelijk is afgeleverd of gelezen, kun je luisteren naar statusupdates met [Webhooks](webhooks.md) — ga niet pollen. De verzendreactie bevestigt alleen dat het bericht is geaccepteerd.

---

## Een bericht verzenden

Er zijn twee manieren om te verzenden. Kies degene die past bij hoe je de contactpersoon al identificeert:

- **Verzenden op contact-ID** — je kent het ID van de contactpersoon al (je hebt de contactpersoon bijvoorbeeld via de API aangemaakt of via een webhook verkregen). Gebruik `POST /contacts/{contactId}/send-message`.
- **Verzenden op contact-identiteit** — je kent het telefoonnummer, Instagram-ID, enz. van de contactpersoon, maar niet hun interne ID. Gebruik `POST /contacts/send` en laat het platform de juiste contactpersoon vinden.

Beide methoden plaatsen het bericht op dezelfde manier in de wachtrij en leveren het af via het kanaal waarop de contactpersoon zich bevindt. Je kiest zelf geen transportmethode — het platform routeert WhatsApp-contacten via WhatsApp, SMS-contacten via SMS, enzovoort.

### Verzenden op contact-ID

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

| Veld | Vereist | Beschrijving |
|---|---|---|
| `body` | Ja | De te verzenden berichttekst. |
| `mediaUrl` | Nee | URL van een mediabestand (afbeelding, document, enz.) om bij te voegen. |
| `mediaContentType` | Nee | MIME-type van de bijgevoegde media, bijv. `image/jpeg`. |
| `pauseBot` | Nee | `true` pauzeert de AI voor dit contact wanneer het bericht wordt verzonden — voor het overnemen door een mens. Zie [De AI pauzeren of hervatten](#pause-or-resume-the-ai-for-one-contact). |
| `clearIncompleteReply` | Nee | `true` verwijdert een halfvoltooid bot-antwoord zodat het niet wordt hervat na uw bericht. |

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

**Antwoord** (`200 OK`):

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

### Verzenden op contact-identiteit

`POST /contacts/send`

Gebruik dit wanneer je niet over het interne ID van de contactpersoon beschikt. Geef de berichttekst `body` op, plus **ofwel** een `contact_id`, **ofwel** een `channel` samen met het identiteitsveld dat overeenkomt met dat kanaal.

| Veld | Vereist | Beschrijving |
|---|---|---|
| `body` | Ja | De te verzenden berichttekst. |
| `contact_id` | Nee | ID van een bestaande contactpersoon. Indien ingesteld, zijn de onderstaande identiteitsvelden niet nodig. |
| `channel` | Nee | Kanaal om via te verzenden. Vereist wanneer `contact_id` niet is opgegeven. Een van de 14 kanalen die geschikt zijn voor uitgaande berichten: `whatsapp`, `whatsapp_web`, `sms`, `instagram`, `instagram_private`, `messenger`, `telegram`, `chat-widget`, `custom`, `email`, `line`, `imessage`, `linkedin`, `viber`. |
| `phone_number` | Nee | Telefoonnummer van de contactpersoon in internationaal formaat. Gebruikt met `whatsapp`, `whatsapp_web` en `sms`. |
| `instagram_id` | Nee | Instagram-gebruikers-ID van de contactpersoon. Gebruikt met `instagram`. |
| `messenger_id` | Nee | Messenger-gebruikers-ID van de contactpersoon. Gebruikt met `messenger`. |
| `telegram_user_id` | Nee | Telegram-gebruikers-ID van de contactpersoon. Gebruikt met `telegram`. |
| `media_url` | Nee | URL van een mediabestand om bij te voegen. |
| `media_content_type` | Nee | MIME-type van de bijgevoegde media, bijv. `image/jpeg`. |

**Welke kanalen kunnen worden opgelost op basis van identiteit.** Slechts zes van de 14 accepteren een identiteitsveld in plaats van een `contact_id`: `whatsapp`, `whatsapp_web` en `sms` worden opgezocht via `phone_number`, `instagram` via `instagram_id`, `messenger` via `messenger_id`, en `telegram` via `telegram_user_id`. De andere acht — `instagram_private`, `chat-widget`, `custom`, `email`, `line`, `imessage`, `linkedin` en `viber` — hebben geen openbare identiteit om op te zoeken, dus verzenden via die kanalen vereist `contact_id`; het alleen doorgeven van `channel` resulteert in een `400` die aangeeft dat `contact_id` vereist is.

**cURL** (met gebruik van de `?apiKey=`-queryvorm)

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

**Antwoord** (`201 Created`):

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

> **Waarom een bericht kan worden geweigerd:** Een contactpersoon met de modus 'niet storen' of privémodus ingeschakeld kan geen uitgaande berichten ontvangen — het verzoek mislukt met een `422`. Als er geen contactpersoon overeenkomt met het ID of de identiteit die je hebt opgegeven, krijg je een `404`.

---

## Berichten van een contactpersoon weergeven

`GET /contacts/{contactId}/messages`

Geeft de berichten van een contactpersoon terug, de nieuwste eerst, met cursor-gebaseerde paginering.

| Query-parameter | Verplicht | Beschrijving |
|---|---|---|
| `limit` | Nee | Paginagrootte. Standaard `50`, maximum `100`. |
| `cursor` | Nee | De `next_cursor`-waarde van een vorig antwoord. Geeft berichten terug die ouder zijn dan de cursor. |
| `filter` | Nee | Filteren op inhoudstype: `all` (standaard), `text`, `media` of `tool_use`. |
| `direction` | Nee | Filteren op richting: `all` (standaard), `inbound` (ontvangen van de contactpersoon) of `outbound` (verzonden door jou). |

> **Opmerking over filteren en paginering:** De `filter`- en `direction`-filters worden toegepast op elke pagina nadat deze is gelezen, dus een gefilterde pagina kan minder items bevatten dan `limit`. De `next_cursor` gaat nog steeds door het volledige gesprek, dus blijf pagineren totdat `next_cursor` gelijk is aan `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"])
```

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

### Berichtvelden

| Veld | Beschrijving |
|---|---|
| `id` | Unieke ID van het bericht. |
| `body` | Tekstinhoud van het bericht. |
| `direction` | `inbound` (ontvangen van de contactpersoon) of `outbound` (verzonden door jouw account). |
| `channel` | Kanaal waarop het bericht is verzonden of ontvangen (bijv. `whatsapp`, `sms`, `instagram`). |
| `status` | Huidige afleverstatus, bijv. `Created`, `sent`, `delivered`, `read`, `failed`. |
| `type` | Berichttype. Berichten met platte tekst hebben een `null` type; geautomatiseerde assistent-toolactiviteit is gemarkeerd als `tool_use`. |
| `timestamp` | ISO 8601-tijdstip waarop het bericht is aangemaakt. |
| `media_url` | URL van een bijgevoegd mediabestand, indien aanwezig. |
| `media_content_type` | MIME-type van de bijgevoegde media, indien aanwezig. |
| `bot_reply` | `true` wanneer het bericht is gegenereerd door de AI-assistent. |
| `score` | Jouw beoordeling van het bericht: `1` duim omhoog, `-1` duim omlaag, `0` wanneer het niet is beoordeeld. Zie [Een bericht beoordelen of een ster geven](#rate-or-star-a-message). |
| `is_important` | `true` wanneer het bericht een ster heeft gekregen. |
| `is_deleted` | `true` wanneer het bericht is verwijderd. Verwijderde berichten blijven in de lijst staan, maar hun `body` en `media_url` zijn leeg. |
| `reactions` | Emoji-reacties op het bericht, van beide kanten. Altijd een array — leeg wanneer er geen zijn. Elk item bevat `emoji`, `from_phone_number`, `from_me` (`true` wanneer de reactie van jou is) en `reacted_at`. |

---

## Chatsessies weergeven

Een chatsessie is één conversatievenster met een contactpersoon: het opent wanneer zij beginnen te praten en sluit wanneer de conversatie is afgerond. Sessies zijn de manier waarop je een lange geschiedenis opdeelt in leesbare conversaties in plaats van één eindeloze lijst.

### Recente sessies voor alle contactpersonen

`GET /chat-sessions/recent`

Geeft de sessies terug die in de afgelopen X uur zijn gestart, nieuwste eerst, voor elke contactpersoon in het account.

| Query-parameter | Vereist | Beschrijving |
|---|---|---|
| `hours` | Ja | Hoeveel uur terug moet worden gekeken. Moet een positief geheel getal zijn. |
| `status` | Nee | Retourneer alleen sessies met deze status: `ChatSessionOpened` of `ChatSessionClosed`. |
| `limit` | Nee | Maximaal aantal sessies om te retourneren. Standaard `100`, maximaal `100`. |
| `includeMessages` | Nee | `true` voegt een `messages` array toe aan elke sessie. Standaard uitgeschakeld omdat het de respons veel groter maakt. |

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

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

### Alle sessies voor één contactpersoon

`GET /chat-sessions/{contactId}`

Geeft elke chatsessie voor één enkele contactpersoon terug. Dezelfde `status`, `limit` en `includeMessages` parameters als hierboven — `hours` is hier niet van toepassing.

**cURL**

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

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

> **Veldnamen voor sessie-ID verschillen tussen de twee endpoints.** De lijst met recente sessies noemt het `session_id` (het bevat ook de details van de contactpersoon, aangezien sessies van vele contactpersonen komen); de lijst per contactpersoon noemt het `id`. Beide waarden zijn wat je doorgeeft als `{sessionId}` bij het ophalen van de volledige thread hieronder.

Wanneer `includeMessages=true`, krijgt elke sessie een `messages` array waarvan de items `id`, `body`, `direction`, `timestamp`, `type`, `channel` en `status` bevatten.

---

## Een chatsessie-thread ophalen

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

Een chatsessie groepeert de berichten van een contactpersoon in één gespreksvenster. Dit eindpunt retourneert de volledige thread van een enkele sessie, **oudste eerst**, samen met de metagegevens van de sessie. U kunt sessie-ID's voor een contactpersoon vinden via de chatsessie-eindpunten.

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

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

Het `session`-object rapporteert `status` (`ChatSessionOpened` indien actief, `ChatSessionClosed` zodra beëindigd), `start_date_time`, `end_date_time` en een voor mensen leesbare `tag`. De `messages`-array gebruikt dezelfde [berichtvelden](#message-fields) als het lijst-eindpunt.

---

## Berichten bewerken, verwijderen en erop reageren

Deze endpoints wijzigen een bericht nadat het is verzonden. Twee ervan bereiken zowel het kanaal van de contactpersoon als je eigen kopie, dus lees de inleiding van de sectie voordat je ze implementeert — wat mogelijk is, hangt volledig af van het kanaal waarop de conversatie plaatsvindt.

**Wat elk kanaal toestaat**

| Actie | Kanalen die de kopie van de contactpersoon kunnen wijzigen | Tijdslimiet |
|---|---|---|
| Een verzonden bericht bewerken | Chatwidget, WhatsApp Web, Telegram, LinkedIn | Geen op de chatwidget, 15 minuten op WhatsApp Web, 48 uur op Telegram, 60 minuten op LinkedIn |
| Voor iedereen verwijderen | Chatwidget, WhatsApp Web, Telegram, LinkedIn | 60 minuten op LinkedIn; de anderen hebben geen gepubliceerde limiet |
| Reageren met een emoji | WhatsApp Web, Telegram | Geen |

Op elk ander kanaal — de WhatsApp Business API, SMS, Instagram, Messenger, e-mail, LINE, aangepaste kanalen — verwijdert een verwijdering het bericht nog steeds uit je inbox, maar behoudt de contactpersoon zijn kopie, en is bewerken of reageren helemaal niet mogelijk.

### Een bericht bewerken

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

Herschrijft een bericht dat je al hebt verzonden, op het apparaat van de contactpersoon en in jouw kopie.

| Veld | Vereist | Beschrijving |
|---|---|---|
| `body` | Ja | De nieuwe berichttekst. Mag niet leeg zijn en mag maximaal 4096 tekens bevatten. |

In tegenstelling tot verwijderen, **mislukt dit duidelijk** wanneer het kanaal weigert: je krijgt een `409` en jouw kopie blijft precies zoals de contactpersoon deze heeft, omdat het tonen van een bewerking die zij nooit hebben ontvangen de twee kanten uit de pas zou laten lopen. Het veld `edit_reason` vertelt je waarom — het bewerkingsvenster van het kanaal is gesloten, het kanaal is losgekoppeld, of er is iets anders misgegaan.

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

**Antwoord** (`200 OK`):

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

Als het kanaal de bewerking niet accepteert, krijg je in plaats daarvan een `409` en is er niets gewijzigd:

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

Een bericht dat al is verwijderd, een kanaal dat helemaal niet kan bewerken, en een bericht dat te oud is voor zijn kanaal retourneren allemaal `400` — het verzoek bereikt het kanaal nooit.

### Eén bericht verwijderen

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

Verwijdert het bericht uit je gesprek en, waar het kanaal dit toestaat, trekt ook de kopie van de contactpersoon in. Geen verzoektekst.

Dit antwoordt altijd met `200` wanneer het bericht bestond, zelfs als de kopie van de contactpersoon niet kon worden ingetrokken — jouw kopie **is** weg, dus een foutmelding zou misleidend zijn. Lees de drie velden in het antwoord om de gebruiker te vertellen wat er daadwerkelijk is gebeurd:

| Veld | Beschrijving |
|---|---|
| `revoke_supported` | Of dit kanaal überhaupt berichten kan intrekken. |
| `revoked` | Of de kopie op het apparaat van de contactpersoon is verwijderd. |
| `revoke_reason` | Waarom het niet is verwijderd, wanneer `revoked` `false` is — bijvoorbeeld `revoke_window_closed` of `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"])
```

**Antwoord** (`200 OK`):

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

> Verwijderde berichten worden niet uit de gespreksgeschiedenis verwijderd. Ze blijven in `GET /contacts/{contactId}/messages` staan met `is_deleted: true` en een lege `body` en `media_url`.

### Meerdere berichten tegelijk verwijderen

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

Wist een reeks berichten alleen aan jouw kant. De inhoud en bijlagen worden geleegd, maar **er wordt niets ingetrokken op het apparaat van de contactpersoon** — om een bericht ook daar terug te halen, verwijder je het één voor één met het bovenstaande eindpunt voor losse berichten.

| Veld | Vereist | Beschrijving |
|---|---|---|
| `message_ids` | Ja | Een niet-lege array van bericht-ID's, tot 500 per verzoek. `messageIds` wordt geaccepteerd als 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"])
```

**Antwoord** (`200 OK`):

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

### Reageren op een bericht

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

Plaatst je eigen emoji-reactie op een bericht, of trekt deze in door een lege tekenreeks te sturen. De reacties van de contactpersoon zelf worden nooit aangepast.

| Veld | Vereist | Beschrijving |
|---|---|---|
| `emoji` | Ja | De emoji om mee te reageren, of `""` om je reactie te verwijderen. Moet een enkele tekenreeks zijn zonder spaties, van maximaal 16 tekens. |

Net als bij bewerken mislukt dit in plaats van een reactie te tonen die de contactpersoon nooit heeft ontvangen, en de foutmelding vertelt je of het de moeite waard is om het opnieuw te proberen:

- `422` — het kan nooit worden afgeleverd in dit gesprek: het kanaal ondersteunt geen reacties, het bericht heeft geen kanaal-ID, of de emoji valt buiten de set die het kanaal toestaat.
- `409` — het kanaal was tijdelijk onbereikbaar. Een nieuwe poging kan werken.

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

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

De `reactions` array is de volledige set reacties die nu op het bericht staan, zowel die van jou als die van de contactpersoon. Bij een `409` of `422` wordt deze ongewijzigd geretourneerd, zodat een client die direct op basis hiervan rendert nooit een reactie toont die niet is afgeleverd.

### Een bericht beoordelen of een ster geven

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

Geeft een bericht een duim omhoog of omlaag en/of markeert het als belangrijk met een ster. Dit is alleen administratie aan jouw kant — er wordt niets naar de contactpersoon verzonden.

| Veld | Vereist | Beschrijving |
|---|---|---|
| `score` | Nee | `1` duim omhoog, `-1` duim omlaag, `0` wist de beoordeling. |
| `is_important` | Nee | `true` geeft het bericht een ster, `false` verwijdert de ster. Moet een echte boolean zijn, niet de string `"true"`. |

Verstuur ten minste een van de twee, anders krijg je een `400`. Alleen wat je verstuurt wordt geschreven, dus het toevoegen van een ster aan een bericht wist nooit de beoordeling en vice versa — en het antwoord bevat alleen de velden die je hebt verstuurd.

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

**Antwoord** (`200 OK`):

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

---

## Berichten als gelezen markeren

U kunt de ongelezen status wissen voor specifieke berichten of voor het hele gesprek.

### Specifieke berichten als gelezen markeren

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

Geef de ID's door van de berichten die als gelezen moeten worden gemarkeerd.

| Veld | Vereist | Beschrijving |
|---|---|---|
| `message_ids` | Ja | Een niet-lege array van bericht-ID's (maximaal 500 per verzoek). |

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

**Antwoord** (`200 OK`):

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

### De hele chat als gelezen markeren

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

Wist de ongelezen-badge voor het volledige gesprek van de contactpersoon in de inbox. Er is geen verzoektekst vereist.

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

**Antwoord** (`200 OK`):

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

### Markeer de hele chat als ongelezen

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

Plaatst de ongelezen-badge terug op het gesprek — handig wanneer iemand in je team een chat heeft geopend maar deze weer overdraagt. Er is geen aanvraagtekst vereist.

Dit is een vlag die alleen voor de inbox geldt: het verandert **niet** wanneer het gesprek voor het laatst is gelezen, dus er wordt geen leesbevestiging verstuurd naar de contactpersoon op kanalen die dit ondersteunen.

**cURL**

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

**Antwoord** (`200 OK`):

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

---

## Exporteer een gesprek

Exportbestanden geven je een volledig gesprek als een leesbaar transcript, in plaats van door berichten te bladeren. Elk export-eindpunt accepteert een `filter` van `all` (standaard), `text`, `media` of `tool_use`, overeenkomend met het filter op de berichtenlijst.

### Exporteer de chat van één contactpersoon

`GET /chat-exports/{contactId}`

| Query-parameter | Vereist | Beschrijving |
|---|---|---|
| `format` | Nee | `txt` (standaard) retourneert een downloadlink naar een platte-teksttranscript. `json` retourneert de berichten als gestructureerde data in het antwoord. |
| `filter` | Nee | `all` (standaard), `text`, `media` of `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"])
```

**Antwoord met `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
      }
    ]
  }
}
```

Met `format=txt` (de standaard), is `data` in plaats daarvan een downloadlink naar het gegenereerde transcriptbestand:

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

> **De downloadlink is tijdelijk.** Haal het bestand op zodra je de link ontvangt in plaats van deze op te slaan — vraag een nieuwe export aan wanneer je het transcript opnieuw nodig hebt.

### Exporteer elk recent gesprek

`GET /chat-exports/recent`

Exporteert de gesprekken van alle contacten die in de afgelopen X uur actief waren, in één aanroep.

| Query-parameter | Vereist | Beschrijving |
|---|---|---|
| `hours` | Ja | Hoeveel uur aan activiteit moet worden teruggekeken. Moet een positief geheel getal zijn. |
| `format` | Nee | `json` (standaard) retourneert één item per contact. `txt` retourneert één downloadbaar tekstbestand met elk gesprek erin. |
| `limit` | Nee | Maximaal aantal contacten om te exporteren. Standaard `50`, maximum `100`. |
| `filter` | Nee | `all` (standaard), `text`, `media` of `tool_use`. |

**cURL**

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

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

Met `format=txt` is het antwoord het tekstbestand zelf, verzonden als een download in plaats van JSON.

> Deze ene aanroep haalt de volledige geschiedenis op van elk overeenkomend contact, dus houd `hours` en `limit` bescheiden bij drukke accounts.

### E-mail een transcript naar de contactpersoon

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

Verstuurt de contactpersoon zijn eigen gespreksverslag per e-mail — de "e-mail mij deze chat"-flow, aangestuurd vanuit je eigen systeem.

| Veld | Vereist | Beschrijving |
|---|---|---|
| `recipient_email` | Nee | Waar het naartoe moet worden gestuurd. Standaard is dit het opgeslagen e-mailadres van de contactpersoon. |
| `via` | Nee | `auto` (standaard) kiest de beste route, `transactional` verstuurt het als een systeeme-mail, `email_channel` verstuurt het vanaf je verbonden e-mailkanaal. |
| `note` | Nee | Een korte regel van jou die boven het transcript wordt getoond. Maximaal 1000 tekens. |

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

**Antwoord** (`200 OK`):

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

`omittedCount` vertelt je hoeveel van de oudste berichten zijn weggelaten om de e-mail op een redelijke lengte te houden. Een `200` betekent dat het transcript is opgesteld en in de wachtrij is geplaatst voor verzending, niet dat het al in de inbox is aangekomen.

---

## De AI voor één contact pauzeren of hervatten

`PUT /contacts/{contactId}`

Zet `is_bot_active` op `false` om te stoppen met het beantwoorden van één contact door de AI, en terug op `true` om het gesprek weer over te dragen. Dit is de overnameschakelaar die u wilt gebruiken wanneer een mens een gesprek overneemt: uitgaande berichten die u met de API verstuurt, worden nog steeds afgeleverd terwijl de bot is gepauzeerd.

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

**Antwoord**

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

**Pauzeren als onderdeel van het antwoord**

Als een mens het overneemt door een antwoord te sturen, kunt u de bot in hetzelfde verzoek pauzeren in plaats van een tweede aanroep te doen. `POST /contacts/{contactId}/send-message` accepteert twee optionele vlaggen:

| Veld | Beschrijving |
|---|---|
| `pauseBot` | `true` pauzeert de AI voor dit contact wanneer het bericht wordt verzonden. |
| `clearIncompleteReply` | `true` verwijdert een halfvoltooid bot-antwoord zodat het daarna niet wordt hervat. |

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

Het antwoord bevat `"botPaused": true` wanneer de pauze is toegepast.

> Een contact markeren als privé met [`POST /contacts/bulk-flag`](contacts.md) pauzeert ook de bot voor hen. Zie [Contacten](contacts.md) voor de volledige lijst met velden.

---

## Uw eigen inbox bouwen

Alles wat een inbox nodig heeft, staat op deze pagina en in [Contacten](contacts.md):

| Wat je nodig hebt | Eindpunt |
|---|---|
| Gesprekken weergeven | `GET /contacts` |
| Een gesprek lezen | `GET /contacts/{contactId}/messages` |
| Chatsessies van een contact weergeven | `GET /chat-sessions/{contactId}` |
| Zien wat er onlangs is binnengekomen | `GET /chat-sessions/recent` |
| Eén chatsessie lezen | `GET /contacts/{contactId}/chat-sessions/{sessionId}/messages` |
| Een handmatig antwoord sturen | `POST /contacts/{contactId}/send-message` |
| Een zojuist verzonden antwoord corrigeren | `POST /contacts/{contactId}/messages/{messageId}/edit` |
| Een bericht verwijderen | `DELETE /contacts/{contactId}/messages/{messageId}` |
| Meerdere berichten wissen | `POST /contacts/{contactId}/messages/bulk-delete` |
| Reageren met een emoji | `POST /contacts/{contactId}/messages/{messageId}/react` |
| Een bericht beoordelen of een ster geven | `PATCH /contacts/{contactId}/messages/{messageId}` |
| Markeren als gelezen | `POST /contacts/{contactId}/mark-read` |
| Een chat teruggeven aan het team | `POST /contacts/{contactId}/mark-unread` |
| Een transcript exporteren | `GET /chat-exports/{contactId}` |
| De AI pauzeren of hervatten | `PUT /contacts/{contactId}` met `is_bot_active` |

Voor live-updates abonneert u zich op de `New Message`, `Replies`, `Human Alerted` en `Chat Concluded` gebeurtenissen met [Webhooks](webhooks.md) in plaats van deze API op een timer te pollen.

---

## Fouten in de Messages API

Bericht-endpoints retourneren de standaard fouten-envelop:

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

| Status | Wanneer dit gebeurt op een bericht-eindpunt |
|---|---|
| `400` | Een vereist veld ontbreekt of een parameter is ongeldig (foutieve `limit`, `hours`, `filter`, `direction`, `status`, een lege of te grote `message_ids`-array, een ongeldige `cursor`, een lege of te lange bewerkings-`body`, een `score` buiten `-1`/`0`/`1`, of een emoji met spaties of meer dan 16 tekens). Wordt ook geretourneerd wanneer een bericht helemaal niet kan worden bewerkt — het is verwijderd, het kanaal ondersteunt geen bewerkingen, of het bewerkingsvenster van dat kanaal is verstreken. |
| `404` | De contactpersoon, chatsessie of een van de opgegeven bericht-ID's is niet gevonden. |
| `409` | Het kanaal accepteert de wijziging op dit moment niet. Er is niets geschreven: bij een bewerking vertelt `edit_reason` waarom; bij een reactie was het kanaal tijdelijk onbereikbaar en kan een nieuwe poging werken. |
| `422` | De contactpersoon kan geen uitgaande berichten ontvangen (niet storen, privé, of een niet-ondersteund kanaal), of een reactie kan nooit worden afgeleverd in dit gesprek (`reaction_reason` zegt welke). |

De gedeelde codes die elk endpoint kan retourneren — `401`, `403` (uw abonnement bevat geen API-toegang), `429` (snelheidslimiet) en `500` — worden vermeld met richtlijnen voor opnieuw proberen in [Fouten & Paginering](errors-and-pagination.md).

---

## Volgende stappen

- [Webhooks](webhooks.md) — ontvang statusupdates over de bezorging in plaats van deze op te vragen.
- [Contacten](contacts.md) — maak contactpersonen aan en zoek ze op naar wie u berichten verstuurt.
- [Afspraken](appointments.md) — boek en beheer afspraken voor uw contactpersonen.
