
# Meddelanden & konversationer

Messages API låter dig skicka ett meddelande till vilken kontakt som helst, läsa en konversation, korrigera eller ta bort ett meddelande du redan skickat, reagera på ett, hämta en fullständig chatt-tråd, exportera en transkription och markera chattar som lästa eller olästa — allt utan att öppna inkorgen.

Alla sökvägar på denna sida är relativa till bas-URL:en `https://api.youraiconnector.com/v1`. Varje anrop kräver din API-nyckel — se [Autentisering](authentication.md) för en fullständig lista över hur den kan skickas. Exemplen nedan använder headern `X-API-Key`, där ett cURL-exempel även visar frågeformuläret `?apiKey=`.

> **Hur leverans fungerar:** Att skicka ett meddelande innebär **inte** att du väntar på att det ska komma fram. API:et tar emot ditt meddelande, svarar omedelbart med ett meddelande-ID och levererar det sedan i bakgrunden via kontaktens kanal (WhatsApp, SMS, Instagram, och så vidare). För att spåra om ett meddelande faktiskt har levererats eller lästs, lyssna efter statusuppdateringar med [Webhooks](webhooks.md) — polla inte. Svaret vid sändning bekräftar endast att meddelandet har tagits emot.

---

## Skicka ett meddelande

Det finns två sätt att skicka. Välj det som passar hur du redan identifierar kontakten:

- **Skicka via kontakt-ID** — du känner redan till kontaktens ID (till exempel om du skapade kontakten via API:et eller fick det från en webhook). Använd `POST /contacts/{contactId}/send-message`.
- **Skicka via kontaktidentitet** — du känner till kontaktens telefonnummer, Instagram-ID, etc., men inte deras interna ID. Använd `POST /contacts/send` och låt plattformen hitta rätt kontakt.

Båda köar meddelandet på samma sätt och levererar det via den kanal kontakten använder. Du väljer inte transport — plattformen dirigerar WhatsApp-kontakter via WhatsApp, SMS-kontakter via SMS, och så vidare.

### Skicka via kontakt-ID

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

| Fält | Krävs | Beskrivning |
|---|---|---|
| `body` | Ja | Meddelandetexten som ska skickas. |
| `mediaUrl` | Nej | URL till en mediefil (bild, dokument, etc.) som ska bifogas. |
| `mediaContentType` | Nej | MIME-typ för den bifogade filen, t.ex. `image/jpeg`. |
| `pauseBot` | Nej | `true` pausar AI:n för denna kontakt när meddelandet skickas — för när en människa tar över. Se [Pausa eller återuppta AI:n](#pause-or-resume-the-ai-for-one-contact). |
| `clearIncompleteReply` | Nej | `true` kastar ett halvfärdigt botsvar så att det inte återupptas efter ditt meddelande. |

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

### Skicka via kontaktidentitet

`POST /contacts/send`

Använd detta när du inte har kontaktens interna ID. Ange meddelandet `body` plus **antingen** ett `contact_id`, **eller** ett `channel` tillsammans med identitetsfältet som matchar den kanalen.

| Fält | Krävs | Beskrivning |
|---|---|---|
| `body` | Ja | Meddelandetexten som ska skickas. |
| `contact_id` | Nej | ID för en befintlig kontakt. När detta är inställt behövs inte identitetsfälten nedan. |
| `channel` | Nej | Kanal att skicka via. Krävs när `contact_id` inte anges. En av de 14 utgående kanalerna: `whatsapp`, `whatsapp_web`, `sms`, `instagram`, `instagram_private`, `messenger`, `telegram`, `chat-widget`, `custom`, `email`, `line`, `imessage`, `linkedin`, `viber`. |
| `phone_number` | Nej | Kontaktens telefonnummer i internationellt format. Används med `whatsapp`, `whatsapp_web` och `sms`. |
| `instagram_id` | Nej | Kontaktens Instagram-användar-ID. Används med `instagram`. |
| `messenger_id` | Nej | Kontaktens Messenger-användar-ID. Används med `messenger`. |
| `telegram_user_id` | Nej | Kontaktens Telegram-användar-ID. Används med `telegram`. |
| `media_url` | Nej | URL till en mediefil som ska bifogas. |
| `media_content_type` | Nej | MIME-typ för bifogad media, t.ex. `image/jpeg`. |

**Vilka kanaler som kan identifieras via identitet.** Endast sex av de 14 accepterar ett identitetsfält istället för ett `contact_id`: `whatsapp`, `whatsapp_web` och `sms` slås upp via `phone_number`, `instagram` via `instagram_id`, `messenger` via `messenger_id`, och `telegram` via `telegram_user_id`. De övriga åtta — `instagram_private`, `chat-widget`, `custom`, `email`, `line`, `imessage`, `linkedin` och `viber` — har ingen publik identitet att slå upp, så att skicka via dessa kanaler kräver `contact_id`; om du bara skickar `channel` returneras ett `400` som meddelar att `contact_id` krävs.

**cURL** (använder frågeformuläret `?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"])
```

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

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

> **Varför ett meddelande kan avvisas:** En kontakt med stör ej-läge eller privat läge aktiverat kan inte ta emot utgående meddelanden — begäran misslyckas med ett `422`. Om ingen kontakt matchar ID:t eller identiteten du angav får du ett `404`.

---

## Lista en kontakts meddelanden

`GET /contacts/{contactId}/messages`

Returnerar en kontakts meddelanden, nyast först, med markörbaserad paginering.

| Frågeparameter | Krävs | Beskrivning |
|---|---|---|
| `limit` | Nej | Sidstorlek. Standard `50`, max `100`. |
| `cursor` | Nej | `next_cursor`-värdet från ett tidigare svar. Returnerar meddelanden äldre än markören. |
| `filter` | Nej | Filtrera efter innehållstyp: `all` (standard), `text`, `media` eller `tool_use`. |
| `direction` | Nej | Filtrera efter riktning: `all` (standard), `inbound` (mottaget från kontakten) eller `outbound` (skickat av dig). |

> **Notering om filtrering och paginering:** `filter`- och `direction`-filtren tillämpas på varje sida efter att den lästs, så en filtrerad sida kan innehålla färre objekt än `limit`. `next_cursor` fortsätter fortfarande genom hela konversationen, så fortsätt paginera tills `next_cursor` är `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"
}
```

### Meddelandefält

| Fält | Beskrivning |
|---|---|
| `id` | Unikt ID för meddelandet. |
| `body` | Textinnehåll i meddelandet. |
| `direction` | `inbound` (mottaget från kontakten) eller `outbound` (skickat av ditt konto). |
| `channel` | Kanal som meddelandet skickades eller mottogs på (t.ex. `whatsapp`, `sms`, `instagram`). |
| `status` | Aktuell leveransstatus, t.ex. `Created`, `sent`, `delivered`, `read`, `failed`. |
| `type` | Meddelandetyp. Vanliga textmeddelanden har typen `null`; automatiserad assistentverktygsaktivitet markeras som `tool_use`. |
| `timestamp` | ISO 8601-tid då meddelandet skapades. |
| `media_url` | URL till en bifogad mediefil, om någon. |
| `media_content_type` | MIME-typ för bifogad media, om någon. |
| `bot_reply` | `true` när meddelandet genererades av AI-assistenten. |
| `score` | Ditt betyg av meddelandet: `1` tumme upp, `-1` tumme ned, `0` när det inte har betygsatts. Se [Betygsätt eller stjärnmarkera ett meddelande](#rate-or-star-a-message). |
| `is_important` | `true` när meddelandet har stjärnmarkerats. |
| `is_deleted` | `true` när meddelandet har tagits bort. Borttagna meddelanden stannar kvar i listan men deras `body` och `media_url` är tomma. |
| `reactions` | Emoji-reaktioner på meddelandet, från båda sidor. Alltid en array — tom när det inte finns några. Varje post har `emoji`, `from_phone_number`, `from_me` (`true` när reaktionen är din) och `reacted_at`. |

---

## Lista chattsessioner

En chattsession är ett konversationsfönster med en kontakt: det öppnas när de börjar prata och stängs när konversationen är avslutad. Sessioner är hur du delar upp en lång historik i läsbara konversationer istället för en oändlig lista.

### Senaste sessioner för alla kontakter

`GET /chat-sessions/recent`

Returnerar de sessioner som startade under de senaste X timmarna, nyast först, för varje kontakt på kontot.

| Frågeparameter | Krävs | Beskrivning |
|---|---|---|
| `hours` | Ja | Hur många timmar att se tillbaka. Måste vara ett positivt heltal. |
| `status` | Nej | Returnera endast sessioner med denna status: `ChatSessionOpened` eller `ChatSessionClosed`. |
| `limit` | Nej | Maximalt antal sessioner att returnera. Standard `100`, maximalt `100`. |
| `includeMessages` | Nej | `true` lägger till en `messages`-array till varje session. Avstängd som standard eftersom det gör svaret mycket 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"
      }
    ]
  }
}
```

### Alla sessioner för en kontakt

`GET /chat-sessions/{contactId}`

Returnerar varje chattsession för en enskild kontakt. Samma parametrar `status`, `limit` och `includeMessages` som ovan — `hours` gäller inte här.

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

> **Fältnamn för sessions-ID skiljer sig mellan de två slutpunkterna.** Listan över senaste sessioner kallar det `session_id` (det innehåller även kontaktens detaljer, eftersom sessioner kommer från många kontakter); listan per kontakt kallar det `id`. Vilket värde som helst är det du skickar som `{sessionId}` när du hämtar hela tråden nedan.

När `includeMessages=true`, får varje session en `messages`-array vars poster innehåller `id`, `body`, `direction`, `timestamp`, `type`, `channel` och `status`.

---

## Hämta en chatt-sessionstråd

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

En chattsession grupperar en kontakts meddelanden i ett konversationsfönster. Denna slutpunkt returnerar hela tråden för en enskild session, **äldst först**, tillsammans med sessionens metadata. Du kan hitta sessions-ID:n för en kontakt via slutpunkterna för chattsessioner.

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

Objektet `session` rapporterar `status` (`ChatSessionOpened` medan aktiv, `ChatSessionClosed` när den avslutats), `start_date_time`, `end_date_time` och en läsbar `tag`. Matrisen `messages` använder samma [meddelandefält](#message-fields) som listslutpunkten.

---

## Redigera, ta bort och reagera på meddelanden

Dessa slutpunkter ändrar ett meddelande efter att det har skickats. Två av dem når ut till kontaktens kanal såväl som din egen kopia, så läs sektionsintroduktionen innan du kopplar ihop dem — vad som är möjligt beror helt på vilken kanal konversationen sker på.

**Vad varje kanal tillåter**

| Åtgärd | Kanaler som kan ändra kontaktens kopia | Tidsgräns |
|---|---|---|
| Redigera ett skickat meddelande | Chattwidget, WhatsApp Web, Telegram, LinkedIn | Ingen för chattwidget, 15 minuter på WhatsApp Web, 48 timmar på Telegram, 60 minuter på LinkedIn |
| Ta bort för alla | Chattwidget, WhatsApp Web, Telegram, LinkedIn | 60 minuter på LinkedIn; de andra har ingen publicerad gräns |
| Reagera med en emoji | WhatsApp Web, Telegram | Ingen |

På alla andra kanaler — WhatsApp Business API, SMS, Instagram, Messenger, e-post, LINE, anpassade kanaler — tar en borttagning fortfarande bort meddelandet från din inkorg, men kontakten behåller sin kopia, och det är inte möjligt att redigera eller reagera alls.

### Redigera ett meddelande

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

Skriver om ett meddelande du redan har skickat, på kontaktens enhet och i din kopia.

| Fält | Krävs | Beskrivning |
|---|---|---|
| `body` | Ja | Den nya meddelandetexten. Får inte vara tom och kan vara högst 4096 tecken. |

Till skillnad från borttagning **misslyckas detta tydligt** när kanalen nekar: du får ett `409` och din kopia lämnas exakt som kontakten har den, eftersom att visa en redigering de aldrig tog emot skulle göra att de två sidorna inte stämmer överens. Fältet `edit_reason` talar om varför — kanalens redigeringsfönster har stängts, kanalen är frånkopplad eller något annat gick fel.

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

Om kanalen inte accepterar redigeringen får du ett `409` istället, och ingenting ändrades:

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

Ett meddelande som redan har tagits bort, en kanal som inte kan redigera alls, och ett meddelande som är för gammalt för sin kanal returnerar alla `400` — förfrågan når aldrig kanalen.

### Ta bort ett meddelande

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

Tar bort meddelandet från din konversation och, där kanalen tillåter det, drar även tillbaka kontaktens kopia. Ingen förfrågningskropp.

Detta svarar alltid `200` när meddelandet existerade, även om kontaktens kopia inte kunde dras tillbaka — din kopia **är** borta, så ett felmeddelande skulle vara missvisande. Läs de tre fälten i svaret för att berätta för användaren vad som faktiskt hände:

| Fält | Beskrivning |
|---|---|
| `revoke_supported` | Huruvida denna kanal överhuvudtaget kan dra tillbaka meddelanden. |
| `revoked` | Huruvida kopian på kontaktens enhet togs bort. |
| `revoke_reason` | Varför den inte togs bort, när `revoked` är `false` — till exempel `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"
}
```

> Borttagna meddelanden tas inte bort från konversationshistoriken. De stannar kvar i `GET /contacts/{contactId}/messages` med `is_deleted: true` och ett tomt `body` och `media_url`.

### Ta bort flera meddelanden samtidigt

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

Rensar en grupp meddelanden endast från din sida. Innehållet och bilagorna töms, men **ingenting dras tillbaka på kontaktens enhet** – för att även dra tillbaka ett meddelande, ta bort det ett i taget med slutpunkten för enstaka meddelanden ovan.

| Fält | Krävs | Beskrivning |
|---|---|---|
| `message_ids` | Ja | En icke-tom matris med meddelande-ID:n, upp till 500 per begäran. `messageIds` accepteras som ett 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
}
```

### Reagera på ett meddelande

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

Placerar din egen emoji-reaktion på ett meddelande, eller tar tillbaka den genom att skicka en tom sträng. Kontaktens egna reaktioner rörs aldrig.

| Fält | Krävs | Beskrivning |
|---|---|---|
| `emoji` | Ja | Emojin att reagera med, eller `""` för att ta bort din reaktion. Måste vara en enstaka sträng utan mellanslag, högst 16 tecken lång. |

Precis som vid redigering misslyckas detta hellre än att visa en reaktion som kontakten aldrig fick, och felet talar om för dig om det är värt att försöka igen:

- `422` — det kan aldrig levereras i denna konversation: kanalen stöder inte reaktioner, meddelandet har inget kanal-ID, eller emojin ligger utanför den uppsättning som kanalen tillåter.
- `409` — kanalen var tillfälligt oåtkomlig. Ett nytt försök kan fungera.

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

Matrisen `reactions` är den fullständiga uppsättningen reaktioner som nu finns på meddelandet, både dina och kontaktens. Vid ett `409` eller `422` returneras den oförändrad, så en klient som renderar direkt från den visar aldrig en reaktion som inte levererades.

### Betygsätt eller stjärnmarkera ett meddelande

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

Betygsätter ett meddelande med tumme upp eller tumme ner och/eller stjärnmarkerar det som viktigt. Detta är endast bokföring på din sida – ingenting skickas till kontakten.

| Fält | Krävs | Beskrivning |
|---|---|---|
| `score` | Nej | `1` tumme upp, `-1` tumme ner, `0` rensar betyget. |
| `is_important` | Nej | `true` stjärnmarkerar meddelandet, `false` tar bort stjärnan. Måste vara ett faktiskt booleskt värde, inte strängen `"true"`. |

Skicka minst ett av de två, annars får du ett `400`. Endast det du skickar skrivs, så att stjärnmarkera ett meddelande rensar aldrig dess betyg och vice versa — och svaret återspeglar endast de fält du skickade.

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

---

## Markera meddelanden som lästa

Du kan rensa oläst-statusen antingen för specifika meddelanden eller för hela konversationen.

### Markera specifika meddelanden som lästa

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

Skicka med ID:n för de meddelanden som ska markeras som lästa.

| Fält | Krävs | Beskrivning |
|---|---|---|
| `message_ids` | Ja | En icke-tom matris med meddelande-ID:n (upp till 500 per förfrågan). |

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

### Markera hela chatten som läst

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

Rensar oläst-markeringen för kontaktens hela konversation i inkorgen. Ingen förfrågningskropp krävs.

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

### Markera hela chatten som oläst

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

Sätter tillbaka oläst-markeringen på konversationen — praktiskt när någon i ditt team har öppnat en chatt men lämnar över den igen. Ingen förfrågningskropp krävs.

Detta är en flagga som endast gäller inkorgen: den ändrar **inte** när konversationen senast lästes, så inget läskvitto skickas till kontakten på kanaler som stöder 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"
}
```

---

## Exportera en konversation

Exporter ger dig en hel konversation som en läsbar transkription, istället för att bläddra igenom meddelanden. Varje export-endpoint accepterar en `filter` av `all` (standard), `text`, `media` eller `tool_use`, som matchar filtret i meddelandelistan.

### Exportera en kontakts chatt

`GET /chat-exports/{contactId}`

| Frågeparameter | Krävs | Beskrivning |
|---|---|---|
| `format` | Nej | `txt` (standard) returnerar en nedladdningslänk till en transkription i klartext. `json` returnerar meddelandena som strukturerad 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), är `data` istället en nedladdningslänk till den genererade transkriptionsfilen:

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

> **Nedladdningslänken är kortlivad.** Hämta filen så snart du får länken istället för att lagra den — begär en ny export när du behöver transkriptionen igen.

### Exportera alla senaste konversationer

`GET /chat-exports/recent`

Exporterar konversationer för alla kontakter som varit aktiva under de senaste X timmarna, i ett anrop.

| Frågeparameter | Krävs | Beskrivning |
|---|---|---|
| `hours` | Ja | Hur många timmars aktivitet som ska sökas igenom. Måste vara ett positivt heltal. |
| `format` | Nej | `json` (standard) returnerar en post per kontakt. `txt` returnerar en enda nedladdningsbar textfil med varje konversation i. |
| `limit` | Nej | Maximalt antal kontakter att exportera. Standard `50`, maximalt `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` är svaret själva textfilen, som skickas som en nedladdning istället för JSON.

> Detta anrop hämtar hela historiken för varje matchande kontakt, så håll `hours` och `limit` måttliga på konton med hög aktivitet.

### E-posta en transkription till kontakten

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

Skickar kontaktens egen konversationstranskription via e-post — "e-posta mig denna chatt"-flödet, drivet från ditt eget system.

| Fält | Krävs | Beskrivning |
|---|---|---|
| `recipient_email` | Nej | Vart den ska skickas. Som standard används kontaktens lagrade e-postadress. |
| `via` | Nej | `auto` (standard) väljer den bästa vägen, `transactional` skickar det som ett system-e-postmeddelande, `email_channel` skickar det från din anslutna e-postkanal. |
| `note` | Nej | En kort rad från dig som visas ovanför transkriptionen. Upp till 1000 tecken. |

**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` anger hur många av de äldsta meddelandena som utelämnades för att hålla e-postmeddelandet i en rimlig längd. Ett `200` innebär att transkriptionen skapades och köades för sändning, inte att den har landat i inkorgen än.

---

## Pausa eller återuppta AI:n för en kontakt

`PUT /contacts/{contactId}`

Sätt `is_bot_active` till `false` för att stoppa AI:n från att svara en kontakt, och tillbaka till `true` för att lämna tillbaka konversationen. Detta är växeln du vill använda när en människa kliver in i en konversation: utgående meddelanden som du skickar via API:et levereras fortfarande medan boten är pausad.

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

**Pausa som en del av svaret**

Om en människa tar över genom att skicka ett svar kan du pausa boten i samma anrop istället för att göra ett andra anrop. `POST /contacts/{contactId}/send-message` accepterar två valfria flaggor:

| Fält | Beskrivning |
|---|---|
| `pauseBot` | `true` pausar AI:n för denna kontakt när meddelandet skickas. |
| `clearIncompleteReply` | `true` kastar ett halvfärdigt botsvar så att det inte återupptas efteråt. |

```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 inkluderar `"botPaused": true` när pausen har tillämpats.

> Att markera en kontakt som privat med [`POST /contacts/bulk-flag`](contacts.md) pausar även boten för dem. Se [Kontakter](contacts.md) för hela listan över fält.

---

## Bygga din egen inkorg

Allt en inkorg behöver finns på denna sida och i [Kontakter](contacts.md):

| Vad du behöver | Slutpunkt |
|---|---|
| Lista konversationer | `GET /contacts` |
| Läs en konversation | `GET /contacts/{contactId}/messages` |
| Lista en kontakts chattsessioner | `GET /chat-sessions/{contactId}` |
| Se vad som kom in nyligen | `GET /chat-sessions/recent` |
| Läs en chattsession | `GET /contacts/{contactId}/chat-sessions/{sessionId}/messages` |
| Skicka ett manuellt svar | `POST /contacts/{contactId}/send-message` |
| Korrigera ett svar du precis skickat | `POST /contacts/{contactId}/messages/{messageId}/edit` |
| Ta bort ett meddelande | `DELETE /contacts/{contactId}/messages/{messageId}` |
| Rensa flera meddelanden | `POST /contacts/{contactId}/messages/bulk-delete` |
| Reagera med en emoji | `POST /contacts/{contactId}/messages/{messageId}/react` |
| Betygsätt eller stjärnmärk ett meddelande | `PATCH /contacts/{contactId}/messages/{messageId}` |
| Markera som läst | `POST /contacts/{contactId}/mark-read` |
| Lämna tillbaka en chatt till teamet | `POST /contacts/{contactId}/mark-unread` |
| Exportera en transkription | `GET /chat-exports/{contactId}` |
| Pausa eller återuppta AI:n | `PUT /contacts/{contactId}` med `is_bot_active` |

För live-uppdateringar, prenumerera på händelserna `New Message`, `Replies`, `Human Alerted` och `Chat Concluded` med [Webhooks](webhooks.md) istället för att polla detta API med en timer.

---

## Fel i Messages API

Meddelandeslutpunkter returnerar standardfel-kuvertet:

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

| Status | När det händer på en meddelandeslutpunkt |
|---|---|
| `400` | Ett obligatoriskt fält saknas eller en parameter är ogiltig (felaktig `limit`, `hours`, `filter`, `direction`, `status`, en tom eller över 500 `message_ids`-array, en ogiltig `cursor`, en tom eller för lång redigerings-`body`, en `score` utanför `-1`/`0`/`1`, eller en emoji med mellanslag eller över 16 tecken). Returneras även när ett meddelande inte kan redigeras alls — det raderades, dess kanal har ingen redigering, eller det är förbi kanalens redigeringsfönster. |
| `404` | Kontakten, chattsessionen eller ett av de angivna meddelande-ID:na hittades inte. |
| `409` | Kanalen kunde inte ta emot ändringen just nu. Inget skrevs: vid en redigering anger `edit_reason` varför; vid en reaktion var kanalen tillfälligt oåtkomlig och ett nytt försök kan fungera. |
| `422` | Kontakten kan inte ta emot utgående meddelanden (stör ej, privat eller en kanal som inte stöds), eller en reaktion kan aldrig levereras i denna konversation (`reaction_reason` anger vilken). |

De delade koderna som alla slutpunkter kan returnera — `401`, `403` (din plan inkluderar inte API-åtkomst), `429` (hastighetsbegränsning) och `500` — listas med vägledning för återförsök i [Fel & Paginering](errors-and-pagination.md).

---

## Nästa steg

- [Webhooks](webhooks.md) — få uppdateringar om leveransstatus skickade till dig istället för att polla.
- [Kontakter](contacts.md) — skapa och slå upp kontakterna du skickar meddelanden till.
- [Bokningar](appointments.md) — boka och hantera bokningar för dina kontakter.
