
# Beskeder & samtaler

Messages API'et lader dig sende en besked til enhver kontakt, læse en samtale igennem, rette eller fjerne en besked, du allerede har sendt, reagere på en besked, hente en fuld chat-sessionstråd, eksportere en transskription og markere chats som læste eller ulæste — alt sammen uden at åbne indbakken.

Alle stier på denne side er relative til basis-URL'en `https://api.youraiconnector.com/v1`. Hver anmodning kræver din API-nøgle — se [Godkendelse](authentication.md) for den fulde liste over måder at sende den på. Eksemplerne nedenfor bruger `X-API-Key`-headeren, hvor et cURL-eksempel også viser `?apiKey=`-forespørgselsformen.

> **Sådan fungerer levering:** Afsendelse af en besked venter **ikke** på, at den når frem. API'et accepterer din besked, returnerer med det samme et besked-ID og leverer den derefter i baggrunden på kontaktens kanal (WhatsApp, SMS, Instagram osv.). For at spore, om en besked rent faktisk blev leveret eller læst, skal du lytte efter statusopdateringer med [Webhooks](webhooks.md) — lad være med at polle. Svaret ved afsendelse bekræfter kun, at beskeden blev accepteret.

---

## Send en besked

Der er to måder at sende på. Vælg den, der passer til, hvordan du allerede identificerer kontakten:

- **Send via kontakt-ID** — du kender allerede kontaktens ID (f.eks. har du oprettet kontakten via API'et eller fået det fra en webhook). Brug `POST /contacts/{contactId}/send-message`.
- **Send via kontaktidentitet** — du kender kontaktens telefonnummer, Instagram-ID osv., men ikke deres interne ID. Brug `POST /contacts/send` og lad platformen finde den rigtige kontakt.

Begge køer beskeden på samme måde og leverer den på den kanal, kontakten befinder sig på. Du vælger ikke transport — platformen ruter WhatsApp-kontakter via WhatsApp, SMS-kontakter via SMS og så videre.

### Send via kontakt-ID

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

| Felt | Påkrævet | Beskrivelse |
|---|---|---|
| `body` | Ja | Beskedteksten, der skal sendes. |
| `mediaUrl` | Nej | URL til en mediefil (billede, dokument osv.), der skal vedhæftes. |
| `mediaContentType` | Nej | MIME-type for det vedhæftede medie, f.eks. `image/jpeg`. |
| `pauseBot` | Nej | `true` sætter AI'en på pause for denne kontakt, når beskeden sendes — til brug for når et menneske overtager. Se [Sæt AI på pause eller genoptag](#pause-or-resume-the-ai-for-one-contact). |
| `clearIncompleteReply` | Nej | `true` kasserer et halvfærdigt botsvar, så det ikke genoptages efter din besked. |

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

**Svar** (`200 OK`):

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

### Send via kontaktidentitet

`POST /contacts/send`

Brug denne, når du ikke har kontaktens interne ID. Angiv beskedens `body` plus **enten** en `contact_id`, **eller** en `channel` sammen med identitetsfeltet, der matcher den kanal.

| Felt | Påkrævet | Beskrivelse |
|---|---|---|
| `body` | Ja | Beskedteksten, der skal sendes. |
| `contact_id` | Nej | ID på en eksisterende kontakt. Når denne er angivet, er identitetsfelterne nedenfor ikke nødvendige. |
| `channel` | Nej | Kanal, der skal sendes via. Påkrævet når `contact_id` ikke er angivet. En af de 14 udgående kanaler: `whatsapp`, `whatsapp_web`, `sms`, `instagram`, `instagram_private`, `messenger`, `telegram`, `chat-widget`, `custom`, `email`, `line`, `imessage`, `linkedin`, `viber`. |
| `phone_number` | Nej | Kontaktens telefonnummer i internationalt format. Bruges sammen med `whatsapp`, `whatsapp_web` og `sms`. |
| `instagram_id` | Nej | Kontaktens Instagram-bruger-ID. Bruges sammen med `instagram`. |
| `messenger_id` | Nej | Kontaktens Messenger-bruger-ID. Bruges sammen med `messenger`. |
| `telegram_user_id` | Nej | Kontaktens Telegram-bruger-ID. Bruges sammen med `telegram`. |
| `media_url` | Nej | URL til en mediefil, der skal vedhæftes. |
| `media_content_type` | Nej | MIME-type for det vedhæftede medie, f.eks. `image/jpeg`. |

**Hvilke kanaler kan løses via identitet.** Kun seks af de 14 accepterer et identitetsfelt i stedet for et `contact_id`: `whatsapp`, `whatsapp_web` og `sms` opslås via `phone_number`, `instagram` via `instagram_id`, `messenger` via `messenger_id`, og `telegram` via `telegram_user_id`. De andre otte — `instagram_private`, `chat-widget`, `custom`, `email`, `line`, `imessage`, `linkedin` og `viber` — har ingen offentlig identitet, der kan opslås, så afsendelse på disse kanaler kræver `contact_id`; hvis du kun sender `channel`, returneres en `400`, der fortæller dig, at `contact_id` er påkrævet.

**cURL** (ved brug af `?apiKey=` forespørgselsformen)

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

**Svar** (`201 Created`):

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

> **Hvorfor en besked kan blive afvist:** En kontakt med forstyr-ikke eller privat tilstand aktiveret kan ikke modtage udgående beskeder — anmodningen fejler med en `422`. Hvis ingen kontakt matcher det ID eller den identitet, du har angivet, får du en `404`.

---

## Vis en kontakts beskeder

`GET /contacts/{contactId}/messages`

Returnerer en kontakts beskeder, nyeste først, med markør-baseret paginering.

| Forespørgselsparameter | Påkrævet | Beskrivelse |
|---|---|---|
| `limit` | Nej | Sidestørrelse. Standard `50`, maksimum `100`. |
| `cursor` | Nej | `next_cursor`-værdien fra et tidligere svar. Returnerer beskeder ældre end markøren. |
| `filter` | Nej | Filtrer efter indholdstype: `all` (standard), `text`, `media` eller `tool_use`. |
| `direction` | Nej | Filtrer efter retning: `all` (standard), `inbound` (modtaget fra kontakten) eller `outbound` (sendt af dig). |

> **Bemærkning om filtrering og paginering:** `filter`- og `direction`-filtrene anvendes på hver side, efter den er læst, så en filtreret side kan indeholde færre elementer end `limit`. `next_cursor` bevæger sig stadig gennem hele samtalen, så fortsæt med at paginere, indtil `next_cursor` er `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"])
```

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

### Beskedfelter

| Felt | Beskrivelse |
|---|---|
| `id` | Unikt ID for beskeden. |
| `body` | Beskedens tekstindhold. |
| `direction` | `inbound` (modtaget fra kontakten) eller `outbound` (sendt af din konto). |
| `channel` | Kanal, som beskeden blev sendt eller modtaget på (f.eks. `whatsapp`, `sms`, `instagram`). |
| `status` | Nuværende leveringsstatus, f.eks. `Created`, `sent`, `delivered`, `read`, `failed`. |
| `type` | Beskedtype. Almindelige tekstbeskeder har typen `null`; automatiseret værktøjsaktivitet fra assistenten markeres som `tool_use`. |
| `timestamp` | ISO 8601-tidspunkt for, hvornår beskeden blev oprettet. |
| `media_url` | URL til en vedhæftet mediefil, hvis en sådan findes. |
| `media_content_type` | MIME-type for det vedhæftede medie, hvis en sådan findes. |
| `bot_reply` | `true` når beskeden er genereret af AI-assistenten. |
| `score` | Din vurdering af beskeden: `1` tommelfinger op, `-1` tommelfinger ned, `0` når den ikke er blevet vurderet. Se [Vurder eller marker en besked med stjerne](#rate-or-star-a-message). |
| `is_important` | `true` når beskeden er blevet markeret med stjerne. |
| `is_deleted` | `true` når beskeden er blevet slettet. Slettede beskeder bliver i listen, men deres `body` og `media_url` er tomme. |
| `reactions` | Emoji-reaktioner på beskeden fra begge parter. Altid et array — tomt når der ikke er nogen. Hver post har `emoji`, `from_phone_number`, `from_me` (`true` når reaktionen er din) og `reacted_at`. |

---

## List chat-sessioner

En chat-session er et samtalevindue med en kontakt: det åbnes, når de begynder at tale, og lukkes, når samtalen er afsluttet. Sessioner er måden, hvorpå du opdeler en lang historik i læsbare samtaler i stedet for én endeløs liste.

### Seneste sessioner på tværs af alle kontakter

`GET /chat-sessions/recent`

Returnerer de sessioner, der startede inden for de sidste X timer, nyeste først, på tværs af alle kontakter på kontoen.

| Forespørgselsparameter | Påkrævet | Beskrivelse |
|---|---|---|
| `hours` | Ja | Hvor mange timer der skal ses tilbage. Skal være et positivt heltal. |
| `status` | Nej | Returner kun sessioner med denne status: `ChatSessionOpened` eller `ChatSessionClosed`. |
| `limit` | Nej | Maksimalt antal sessioner, der skal returneres. Standard `100`, maksimum `100`. |
| `includeMessages` | Nej | `true` tilføjer et `messages`-array til hver session. Deaktiveret som standard, da det gør svaret meget større. |

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

**Svar** (`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 sessioner for én kontakt

`GET /chat-sessions/{contactId}`

Returnerer hver chat-session for en enkelt kontakt. Samme `status`, `limit` og `includeMessages` parametre som ovenfor — `hours` gælder ikke her.

**cURL**

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

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

> **Feltnavne for session-ID er forskellige mellem de to slutpunkter.** Listen over seneste sessioner kalder det `session_id` (det indeholder også kontaktens detaljer, da sessioner kommer fra mange kontakter); listen pr. kontakt kalder det `id`. Begge værdier er det, du sender som `{sessionId}`, når du henter den fulde tråd nedenfor.

Når `includeMessages=true`, får hver session et `messages`-array, hvis poster indeholder `id`, `body`, `direction`, `timestamp`, `type`, `channel` og `status`.

---

## Hent en chat-sessionstråd

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

En chatsession grupperer en kontakts beskeder i ét samtalevindue. Dette slutpunkt returnerer hele tråden for en enkelt session, **ældste først**, sammen med sessionens metadata. Du kan finde sessions-id'er for en kontakt via slutpunkterne for chatsessioner.

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

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

`session`-objektet rapporterer `status` (`ChatSessionOpened` mens aktiv, `ChatSessionClosed` når afsluttet), `start_date_time`, `end_date_time` og et læsbart `tag`. `messages`-arrayet bruger de samme [beskedfelter](#message-fields) som listeslutpunktet.

---

## Rediger, slet og reager på beskeder

Disse slutpunkter ændrer en besked, efter den er blevet sendt. To af dem kontakter både modtagerens kanal og din egen kopi, så læs sektionsintroduktionen, før du implementerer dem — hvad der er muligt, afhænger udelukkende af den kanal, samtalen foregår på.

**Hvad hver kanal tillader**

| Handling | Kanaler, der kan ændre kontaktens kopi | Tidsgrænse |
|---|---|---|
| Rediger en sendt besked | Chat-widget, WhatsApp Web, Telegram, LinkedIn | Ingen på chat-widget, 15 minutter på WhatsApp Web, 48 timer på Telegram, 60 minutter på LinkedIn |
| Slet for alle | Chat-widget, WhatsApp Web, Telegram, LinkedIn | 60 minutter på LinkedIn; de andre har ingen offentliggjort grænse |
| Reager med en emoji | WhatsApp Web, Telegram | Ingen |

På alle andre kanaler — WhatsApp Business API, SMS, Instagram, Messenger, e-mail, LINE, tilpassede kanaler — fjerner en sletning stadig beskeden fra din indbakke, men kontakten beholder sin kopi, og redigering eller reaktion er slet ikke muligt.

### Rediger en besked

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

Omskriver en besked, du allerede har sendt, på kontaktens enhed og i din kopi.

| Felt | Påkrævet | Beskrivelse |
|---|---|---|
| `body` | Ja | Den nye beskedtekst. Må ikke være tom og kan maksimalt være på 4096 tegn. |

I modsætning til sletning **fejler dette tydeligt**, når kanalen afviser det: du får en `409`, og din kopi forbliver præcis som kontaktens, fordi det ville skabe uoverensstemmelse at vise en redigering, de aldrig har modtaget. Feltet `edit_reason` fortæller dig hvorfor — kanalens redigeringsvindue er lukket, kanalen er afbrudt, eller noget andet gik galt.

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

**Svar** (`200 OK`):

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

Hvis kanalen ikke vil acceptere redigeringen, får du en `409` i stedet, og intet blev ændret:

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

En besked, der allerede er slettet, en kanal, der slet ikke kan redigere, og en besked, der er for gammel til sin kanal, returnerer alle `400` — anmodningen når aldrig frem til kanalen.

### Slet én besked

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

Fjerner beskeden fra din samtale og trækker, hvor kanalen tillader det, også kontaktens kopi tilbage. Ingen anmodningstekst.

Dette svarer altid `200`, når beskeden eksisterede, selvom kontaktens kopi ikke kunne trækkes tilbage — din kopi **er** væk, så en fejl ville være vildledende. Læs de tre felter i svaret for at fortælle brugeren, hvad der faktisk skete:

| Felt | Beskrivelse |
|---|---|
| `revoke_supported` | Om denne kanal overhovedet kan trække beskeder tilbage. |
| `revoked` | Om kopien på kontaktens enhed blev fjernet. |
| `revoke_reason` | Hvorfor den ikke blev fjernet, når `revoked` er `false` — for eksempel `revoke_window_closed` eller `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"])
```

**Svar** (`200 OK`):

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

> Slettede beskeder fjernes ikke fra samtaleloggen. De forbliver i `GET /contacts/{contactId}/messages` med `is_deleted: true` og en tom `body` og `media_url`.

### Slet flere beskeder på én gang

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

Rydder en gruppe beskeder kun fra din side. Indholdet og vedhæftede filer tømmes, men **intet trækkes tilbage på kontaktens enhed** — for også at trække en besked tilbage, skal du slette den én efter én med slutpunktet for enkelte beskeder ovenfor.

| Felt | Påkrævet | Beskrivelse |
|---|---|---|
| `message_ids` | Ja | Et ikke-tomt array af besked-id'er, op til 500 pr. anmodning. `messageIds` accepteres som et 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"])
```

**Svar** (`200 OK`):

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

### Reager på en besked

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

Tilføjer din egen emoji-reaktion på en besked, eller fjerner den ved at sende en tom streng. Kontaktens egne reaktioner bliver aldrig berørt.

| Felt | Påkrævet | Beskrivelse |
|---|---|---|
| `emoji` | Ja | Den emoji, der skal reageres med, eller `""` for at fjerne din reaktion. Skal være en enkelt streng uden mellemrum, på højst 16 tegn. |

Ligesom ved redigering fejler dette frem for at vise en reaktion, som kontakten aldrig har modtaget, og fejlen fortæller dig, om det kan betale sig at prøve igen:

- `422` — den kan aldrig leveres i denne samtale: kanalen understøtter ikke reaktioner, beskeden har intet kanal-id, eller emojien er uden for det sæt, som kanalen tillader.
- `409` — kanalen var midlertidigt utilgængelig. Et nyt forsøg kan virke.

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

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

`reactions`-arrayet er det fulde sæt af reaktioner, der nu er på beskeden, både dine og kontaktens. Ved en `409` eller `422` returneres det uændret, så en klient, der renderer direkte ud fra det, aldrig viser en reaktion, der ikke blev leveret.

### Bedøm eller stjernemarkér en besked

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

Giver en besked en tommelfinger op eller ned og/eller markerer den som vigtig. Dette er kun bogføring på din side — intet sendes til kontakten.

| Felt | Påkrævet | Beskrivelse |
|---|---|---|
| `score` | Nej | `1` tommelfinger op, `-1` tommelfinger ned, `0` rydder bedømmelsen. |
| `is_important` | Nej | `true` markerer beskeden med stjerne, `false` fjerner stjernen. Skal være en reel boolsk værdi, ikke strengen `"true"`. |

Send mindst én af de to, ellers får du en `400`. Kun det, du sender, bliver skrevet, så at markere en besked med stjerne rydder aldrig dens bedømmelse og omvendt — og svaret sender kun de felter tilbage, som du sendte.

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

**Svar** (`200 OK`):

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

---

## Markér beskeder som læst

Du kan fjerne ulæst-status enten for specifikke beskeder eller for hele samtalen.

### Markér specifikke beskeder som læst

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

Angiv id'erne for de beskeder, der skal markeres som læst.

| Felt | Påkrævet | Beskrivelse |
|---|---|---|
| `message_ids` | Ja | Et ikke-tomt array af besked-id'er (op til 500 pr. anmodning). |

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

**Svar** (`200 OK`):

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

### Markér hele chatten som læst

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

Fjerner ulæst-ikonet for kontaktens samlede samtale i indbakken. Der kræves ingen anmodningstekst.

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

**Svar** (`200 OK`):

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

### Marker hele chatten som ulæst

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

Sætter det ulæste-badge tilbage på samtalen — praktisk når en person på dit team har åbnet en chat, men giver den videre. Der kræves ingen anmodningstekst.

Dette er et flag, der kun gælder indbakken: det ændrer **ikke**, hvornår samtalen sidst blev læst, så der sendes ingen læsekvittering til kontakten på kanaler, der understøtter dem.

**cURL**

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

**Svar** (`200 OK`):

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

---

## Eksporter en samtale

Eksport giver dig en hel samtale som en læsbar udskrift i stedet for at skulle bladre gennem beskeder. Hvert eksport-endepunkt accepterer en `filter` af `all` (standard), `text`, `media` eller `tool_use`, svarende til filteret på beskedlisten.

### Eksporter én kontakts chat

`GET /chat-exports/{contactId}`

| Forespørgselsparameter | Påkrævet | Beskrivelse |
|---|---|---|
| `format` | Nej | `txt` (standard) returnerer et downloadlink til en udskrift i almindelig tekst. `json` returnerer beskederne som strukturerede data i svaret. |
| `filter` | Nej | `all` (standard), `text`, `media` eller `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"])
```

**Svar med `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
      }
    ]
  }
}
```

Med `format=txt` (standard), er `data` i stedet et downloadlink til den genererede udskriftsfil:

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

> **Downloadlinket er kortvarigt.** Hent filen så snart du får linket i stedet for at gemme det — anmod om en ny eksport, når du har brug for udskriften igen.

### Eksporter alle nylige samtaler

`GET /chat-exports/recent`

Eksporterer samtalerne for alle kontakter, der har været aktive inden for de sidste X timer, i ét kald.

| Forespørgselsparameter | Påkrævet | Beskrivelse |
|---|---|---|
| `hours` | Ja | Hvor mange timers aktivitet der skal ses tilbage på. Skal være et positivt heltal. |
| `format` | Nej | `json` (standard) returnerer én post pr. kontakt. `txt` returnerer en enkelt downloadbar tekstfil med alle samtaler i. |
| `limit` | Nej | Maksimalt antal kontakter, der skal eksporteres. Standard `50`, maksimum `100`. |
| `filter` | Nej | `all` (standard), `text`, `media` eller `tool_use`. |

**cURL**

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

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

Med `format=txt` er svaret selve tekstfilen, der sendes som en download i stedet for JSON.

> Dette ene kald henter hele historikken for hver matchende kontakt, så hold `hours` og `limit` på et moderat niveau på travle konti.

### Send en transskription til kontakten via e-mail

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

Sender kontakten deres egen samtaletransskription via e-mail — "send mig denne chat"-flowet, drevet fra dit eget system.

| Felt | Påkrævet | Beskrivelse |
|---|---|---|
| `recipient_email` | Nej | Hvor den skal sendes hen. Som standard bruges kontaktens gemte e-mailadresse. |
| `via` | Nej | `auto` (standard) vælger den bedste rute, `transactional` sender den som en system-e-mail, `email_channel` sender den fra din tilsluttede e-mailkanal. |
| `note` | Nej | En kort linje fra dig, der vises over transskriptionen. Op til 1000 tegn. |

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

**Svar** (`200 OK`):

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

`omittedCount` fortæller dig, hvor mange af de ældste beskeder der blev udeladt for at holde e-mailen i en fornuftig længde. En `200` betyder, at transskriptionen blev oprettet og sat i kø til afsendelse, ikke at den er landet i indbakken endnu.

---

## Sæt AI på pause eller genoptag for én kontakt

`PUT /contacts/{contactId}`

Sæt `is_bot_active` til `false` for at stoppe AI'en i at svare en kontakt, og tilbage til `true` for at give samtalen tilbage. Dette er den overtagelseskontakt, du skal bruge, når et menneske træder ind i en samtale: udgående beskeder, du sender med API'en, leveres stadig, mens botten er sat på pause.

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

**Svar**

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

**Pause som en del af svaret**

Hvis et menneske overtager ved at sende et svar, kan du sætte botten på pause i den samme anmodning i stedet for at foretage et andet kald. `POST /contacts/{contactId}/send-message` accepterer to valgfrie flag:

| Felt | Beskrivelse |
|---|---|
| `pauseBot` | `true` sætter AI'en på pause for denne kontakt, når beskeden sendes. |
| `clearIncompleteReply` | `true` kasserer et halvfærdigt botsvar, så det ikke genoptages bagefter. |

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

Svaret inkluderer `"botPaused": true`, når pausen blev anvendt.

> Markering af en kontakt som privat med [`POST /contacts/bulk-flag`](contacts.md) sætter også botten på pause for dem. Se [Kontakter](contacts.md) for den fulde liste over felter.

---

## Byg din egen indbakke

Alt, hvad en indbakke har brug for, findes på denne side og i [Kontakter](contacts.md):

| Hvad du har brug for | Endpoint |
|---|---|
| List samtaler | `GET /contacts` |
| Læs en samtale | `GET /contacts/{contactId}/messages` |
| List en kontakts chatsessioner | `GET /chat-sessions/{contactId}` |
| Se hvad der for nylig er kommet ind | `GET /chat-sessions/recent` |
| Læs én chatsession | `GET /contacts/{contactId}/chat-sessions/{sessionId}/messages` |
| Send et manuelt svar | `POST /contacts/{contactId}/send-message` |
| Ret et svar, du lige har sendt | `POST /contacts/{contactId}/messages/{messageId}/edit` |
| Fjern en besked | `DELETE /contacts/{contactId}/messages/{messageId}` |
| Ryd flere beskeder | `POST /contacts/{contactId}/messages/bulk-delete` |
| Reager med en emoji | `POST /contacts/{contactId}/messages/{messageId}/react` |
| Vurder eller marker en besked med stjerne | `PATCH /contacts/{contactId}/messages/{messageId}` |
| Marker som læst | `POST /contacts/{contactId}/mark-read` |
| Giv en chat tilbage til teamet | `POST /contacts/{contactId}/mark-unread` |
| Eksporter en transskription | `GET /chat-exports/{contactId}` |
| Sæt AI på pause eller genoptag den | `PUT /contacts/{contactId}` med `is_bot_active` |

For live-opdateringer, abonner på `New Message`, `Replies`, `Human Alerted` og `Chat Concluded` begivenhederne med [Webhooks](webhooks.md) i stedet for at polle denne API på en timer.

---

## Fejl i Messages API

Besked-endpoints returnerer standardfejl-konvolutten:

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

| Status | Hvornår det sker på et besked-endpoint |
|---|---|
| `400` | Et påkrævet felt mangler, eller en parameter er ugyldig (forkert `limit`, `hours`, `filter`, `direction`, `status`, et tomt eller over-500 `message_ids` array, en ugyldig `cursor`, et tomt eller for langt redigerings-`body`, en `score` uden for `-1`/`0`/`1`, eller en emoji med mellemrum eller over 16 tegn). Returneres også, når en besked slet ikke kan redigeres — den blev slettet, dens kanal har ingen redigering, eller den er uden for kanalens redigeringsvindue. |
| `404` | Kontakten, chatsessionen eller et af de angivne besked-ID'er blev ikke fundet. |
| `409` | Kanalen ville ikke acceptere ændringen lige nu. Intet blev skrevet: ved en redigering fortæller `edit_reason` hvorfor; ved en reaktion var kanalen midlertidigt utilgængelig, og et nyt forsøg kan virke. |
| `422` | Kontakten kan ikke modtage udgående beskeder (forstyr ikke, privat eller en ikke-understøttet kanal), eller en reaktion kan aldrig leveres i denne samtale (`reaction_reason` fortæller hvilken). |

De delte koder, som ethvert endpoint kan returnere — `401`, `403` (din plan inkluderer ikke API-adgang), `429` (rate limit) og `500` — er angivet med vejledning om genforsøg i [Errors & Pagination](errors-and-pagination.md).

---

## Næste skridt

- [Webhooks](webhooks.md) — få leveringsstatusopdateringer sendt til dig i stedet for at polle.
- [Kontakter](contacts.md) — opret og find de kontakter, du sender beskeder til.
- [Aftaler](appointments.md) — book og administrer aftaler for dine kontakter.
