
# Wiadomości i konwersacje

Interfejs Messages API umożliwia wysyłanie wiadomości do dowolnego kontaktu, odczytywanie konwersacji, poprawianie lub usuwanie już wysłanej wiadomości, reagowanie na nią, pobieranie pełnego wątku czatu, eksportowanie transkrypcji oraz oznaczanie czatów jako przeczytane lub nieprzeczytane — wszystko to bez otwierania skrzynki odbiorczej.

Wszystkie ścieżki na tej stronie są względne względem podstawowego adresu URL `https://api.youraiconnector.com/v1`. Każde żądanie wymaga Twojego klucza API — zobacz [Uwierzytelnianie](authentication.md), aby uzyskać pełną listę sposobów jego przesyłania. Poniższe przykłady wykorzystują nagłówek `X-API-Key`, a jeden z przykładów cURL pokazuje również formularz zapytania `?apiKey=`.

> **Jak działa dostarczanie:** Wysłanie wiadomości **nie** czeka na jej dotarcie do odbiorcy. API przyjmuje wiadomość, natychmiast zwraca identyfikator wiadomości, a następnie dostarcza ją w tle kanałem kontaktu (WhatsApp, SMS, Instagram itd.). Aby śledzić, czy wiadomość została faktycznie dostarczona lub odczytana, nasłuchuj aktualizacji statusu za pomocą [Webhooków](webhooks.md) — nie używaj odpytywania (polling). Odpowiedź wysyłki potwierdza jedynie, że wiadomość została przyjęta.

---

## Wyślij wiadomość

Istnieją dwa sposoby wysyłania. Wybierz ten, który pasuje do sposobu, w jaki identyfikujesz kontakt:

- **Wysyłka według identyfikatora kontaktu** — znasz już identyfikator kontaktu (na przykład utworzyłeś kontakt przez API lub otrzymałeś go z webhooka). Użyj `POST /contacts/{contactId}/send-message`.
- **Wysyłka według tożsamości kontaktu** — znasz numer telefonu kontaktu, identyfikator Instagrama itp., ale nie znasz jego wewnętrznego identyfikatora. Użyj `POST /contacts/send` i pozwól platformie znaleźć odpowiedni kontakt.

Obie metody ustawiają wiadomość w kolejce w ten sam sposób i dostarczają ją kanałem, z którego korzysta dany kontakt. Nie wybierasz transportu — platforma kieruje kontakty WhatsApp przez WhatsApp, kontakty SMS przez SMS i tak dalej.

### Wyślij według identyfikatora kontaktu

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

| Pole | Wymagane | Opis |
|---|---|---|
| `body` | Tak | Treść wiadomości do wysłania. |
| `mediaUrl` | Nie | URL pliku multimedialnego (obraz, dokument itp.) do załączenia. |
| `mediaContentType` | Nie | Typ MIME załączonych mediów, np. `image/jpeg`. |
| `pauseBot` | Nie | `true` wstrzymuje AI dla tego kontaktu w momencie wysyłania wiadomości — w celu przejęcia rozmowy przez człowieka. Zobacz [Wstrzymywanie lub wznawianie AI](#pause-or-resume-the-ai-for-one-contact). |
| `clearIncompleteReply` | Nie | `true` odrzuca niedokończoną odpowiedź bota, aby nie wznowił jej po Twojej wiadomości. |

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

**Odpowiedź** (`200 OK`):

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

### Wyślij według tożsamości kontaktu

`POST /contacts/send`

Użyj tej metody, gdy nie masz wewnętrznego identyfikatora kontaktu. Podaj treść wiadomości `body` oraz **albo** `contact_id`, **albo** `channel` wraz z polem tożsamości pasującym do danego kanału.

| Pole | Wymagane | Opis |
|---|---|---|
| `body` | Tak | Treść wiadomości do wysłania. |
| `contact_id` | Nie | Identyfikator istniejącego kontaktu. Gdy jest ustawiony, poniższe pola tożsamości nie są potrzebne. |
| `channel` | Nie | Kanał, przez który ma zostać wysłana wiadomość. Wymagane, gdy nie podano `contact_id`. Jeden z 14 kanałów umożliwiających wysyłkę: `whatsapp`, `whatsapp_web`, `sms`, `instagram`, `instagram_private`, `messenger`, `telegram`, `chat-widget`, `custom`, `email`, `line`, `imessage`, `linkedin`, `viber`. |
| `phone_number` | Nie | Numer telefonu kontaktu w formacie międzynarodowym. Używany z `whatsapp`, `whatsapp_web` oraz `sms`. |
| `instagram_id` | Nie | Identyfikator użytkownika Instagram kontaktu. Używany z `instagram`. |
| `messenger_id` | Nie | Identyfikator użytkownika Messenger kontaktu. Używany z `messenger`. |
| `telegram_user_id` | Nie | Identyfikator użytkownika Telegram kontaktu. Używany z `telegram`. |
| `media_url` | Nie | Adres URL pliku multimedialnego do załączenia. |
| `media_content_type` | Nie | Typ MIME załączonego pliku multimedialnego, np. `image/jpeg`. |

**Które kanały można rozpoznać na podstawie tożsamości.** Tylko sześć z 14 akceptuje pole tożsamości zamiast `contact_id`: `whatsapp`, `whatsapp_web` oraz `sms` są wyszukiwane przez `phone_number`, `instagram` przez `instagram_id`, `messenger` przez `messenger_id`, a `telegram` przez `telegram_user_id`. Pozostałe osiem — `instagram_private`, `chat-widget`, `custom`, `email`, `line`, `imessage`, `linkedin` oraz `viber` — nie posiada publicznej tożsamości, którą można wyszukać, więc wysyłanie przez te kanały wymaga `contact_id`; przekazanie samego `channel` spowoduje zwrócenie `400` z informacją, że wymagane jest `contact_id`.

**cURL** (używając formularza zapytania `?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"])
```

**Odpowiedź** (`201 Created`):

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

> **Dlaczego wiadomość może zostać odrzucona:** Kontakt z włączonym trybem „nie przeszkadzać” lub trybem prywatnym nie może otrzymywać wiadomości wychodzących — żądanie kończy się niepowodzeniem z błędem `422`. Jeśli żaden kontakt nie pasuje do podanego ID lub tożsamości, otrzymasz `404`.

---

## Wyświetl wiadomości kontaktu

`GET /contacts/{contactId}/messages`

Zwraca wiadomości kontaktu, od najnowszych, z paginacją opartą na kursorze.

| Parametr zapytania | Wymagane | Opis |
|---|---|---|
| `limit` | Nie | Rozmiar strony. Domyślnie `50`, maksymalnie `100`. |
| `cursor` | Nie | Wartość `next_cursor` z poprzedniej odpowiedzi. Zwraca wiadomości starsze niż kursor. |
| `filter` | Nie | Filtruj według typu zawartości: `all` (domyślnie), `text`, `media` lub `tool_use`. |
| `direction` | Nie | Filtruj według kierunku: `all` (domyślnie), `inbound` (otrzymane od kontaktu) lub `outbound` (wysłane przez Ciebie). |

> **Uwaga dotycząca filtrowania i paginacji:** Filtry `filter` i `direction` są stosowane do każdej strony po jej odczytaniu, więc przefiltrowana strona może zawierać mniej elementów niż `limit`. Wartość `next_cursor` nadal przesuwa się przez pełną konwersację, więc kontynuuj stronicowanie, aż `next_cursor` będzie `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"])
```

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

### Pola wiadomości

| Pole | Opis |
|---|---|
| `id` | Unikalny identyfikator wiadomości. |
| `body` | Treść tekstowa wiadomości. |
| `direction` | `inbound` (otrzymana od kontaktu) lub `outbound` (wysłana z Twojego konta). |
| `channel` | Kanał, przez który wiadomość została wysłana lub odebrana (np. `whatsapp`, `sms`, `instagram`). |
| `status` | Bieżący status dostarczenia, np. `Created`, `sent`, `delivered`, `read`, `failed`. |
| `type` | Typ wiadomości. Wiadomości tekstowe mają typ `null`; aktywność automatycznych narzędzi asystenta jest oznaczona jako `tool_use`. |
| `timestamp` | Czas utworzenia wiadomości w formacie ISO 8601. |
| `media_url` | Adres URL załączonego pliku multimedialnego, jeśli istnieje. |
| `media_content_type` | Typ MIME załączonych multimediów, jeśli istnieją. |
| `bot_reply` | `true`, gdy wiadomość została wygenerowana przez asystenta AI. |
| `score` | Twoja ocena wiadomości: `1` łapka w górę, `-1` łapka w dół, `0`, gdy wiadomość nie została oceniona. Zobacz [Oceń lub oznacz gwiazdką wiadomość](#rate-or-star-a-message). |
| `is_important` | `true`, gdy wiadomość została oznaczona gwiazdką. |
| `is_deleted` | `true`, gdy wiadomość została usunięta. Usunięte wiadomości pozostają na liście, ale ich `body` i `media_url` są puste. |
| `reactions` | Reakcje emoji na wiadomość, z obu stron. Zawsze tablica — pusta, gdy brak reakcji. Każdy wpis zawiera `emoji`, `from_phone_number`, `from_me` (`true`, gdy reakcja jest Twoja) oraz `reacted_at`. |

---

## Lista sesji czatu

Sesja czatu to jedno okno konwersacji z kontaktem: otwiera się, gdy kontakt zaczyna pisać, i zamyka, gdy konwersacja zostaje zakończona. Sesje pozwalają na podział długiej historii na czytelne konwersacje zamiast jednej niekończącej się listy.

### Ostatnie sesje wszystkich kontaktów

`GET /chat-sessions/recent`

Zwraca sesje, które rozpoczęły się w ciągu ostatnich X godzin, od najnowszych, dla wszystkich kontaktów na koncie.

| Parametr zapytania | Wymagany | Opis |
|---|---|---|
| `hours` | Tak | Ile godzin wstecz sprawdzić. Musi być liczbą całkowitą dodatnią. |
| `status` | Nie | Zwróć tylko sesje o tym statusie: `ChatSessionOpened` lub `ChatSessionClosed`. |
| `limit` | Nie | Maksymalna liczba sesji do zwrócenia. Domyślnie `100`, maksymalnie `100`. |
| `includeMessages` | Nie | `true` dodaje tablicę `messages` do każdej sesji. Domyślnie wyłączone, ponieważ znacznie zwiększa rozmiar odpowiedzi. |

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

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

### Wszystkie sesje dla jednego kontaktu

`GET /chat-sessions/{contactId}`

Zwraca każdą sesję czatu dla pojedynczego kontaktu. Te same parametry `status`, `limit` i `includeMessages` co powyżej — `hours` nie ma tutaj zastosowania.

**cURL**

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

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

> **Nazwy pól identyfikatora sesji różnią się między tymi dwoma punktami końcowymi.** Lista ostatnich sesji nazywa go `session_id` (zawiera również szczegóły kontaktu, ponieważ sesje pochodzą od wielu kontaktów); lista dla kontaktu nazywa go `id`. Obie wartości są tym, co przekazujesz jako `{sessionId}` podczas pobierania pełnego wątku poniżej.

Gdy `includeMessages=true`, każda sesja zyskuje tablicę `messages`, której wpisy zawierają `id`, `body`, `direction`, `timestamp`, `type`, `channel` oraz `status`.

---

## Pobierz wątek sesji czatu

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

Sesja czatu grupuje wiadomości kontaktu w jednym oknie konwersacji. Ten punkt końcowy zwraca pełny wątek pojedynczej sesji, **od najstarszej wiadomości**, wraz z metadanymi sesji. Identyfikatory sesji dla danego kontaktu można znaleźć za pomocą punktów końcowych sesji czatu.

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

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

Obiekt `session` zawiera `status` (`ChatSessionOpened` gdy jest aktywna, `ChatSessionClosed` po zakończeniu), `start_date_time`, `end_date_time` oraz czytelny dla człowieka `tag`. Tablica `messages` wykorzystuje te same [pola wiadomości](#message-fields), co punkt końcowy listy.

---

## Edytowanie, usuwanie i reagowanie na wiadomości

Te punkty końcowe zmieniają wiadomość po jej wysłaniu. Dwa z nich komunikują się zarówno z kanałem kontaktu, jak i Twoją kopią, więc przeczytaj wstęp do sekcji przed ich użyciem — to, co jest możliwe, zależy całkowicie od kanału, na którym odbywa się konwersacja.

**Co umożliwia każdy kanał**

| Akcja | Kanały, które mogą zmienić kopię kontaktu | Limit czasu |
|---|---|---|
| Edycja wysłanej wiadomości | Widżet czatu, WhatsApp Web, Telegram, LinkedIn | Brak w widżecie czatu, 15 minut w WhatsApp Web, 48 godzin w Telegramie, 60 minut na LinkedIn |
| Usuń dla wszystkich | Widżet czatu, WhatsApp Web, Telegram, LinkedIn | 60 minut na LinkedIn; pozostałe nie mają opublikowanego limitu |
| Reagowanie emoji | WhatsApp Web, Telegram | Brak |

W przypadku wszystkich innych kanałów — WhatsApp Business API, SMS, Instagram, Messenger, e-mail, LINE, kanały niestandardowe — usunięcie nadal usuwa wiadomość z Twojej skrzynki odbiorczej, ale kontakt zachowuje swoją kopię, a edycja lub reagowanie nie są w ogóle możliwe.

### Edytuj wiadomość

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

Nadpisuje wiadomość, którą już wysłałeś, zarówno na urządzeniu kontaktu, jak i w Twojej kopii.

| Pole | Wymagane | Opis |
|---|---|---|
| `body` | Tak | Nowy tekst wiadomości. Nie może być pusty i może mieć maksymalnie 4096 znaków. |

W przeciwieństwie do usuwania, ta operacja **kończy się wyraźnym błędem**, gdy kanał odmawia: otrzymujesz `409`, a Twoja kopia pozostaje dokładnie taka sama, jak u kontaktu, ponieważ pokazanie edycji, której nigdy nie otrzymali, spowodowałoby rozbieżność między stronami. Pole `edit_reason` informuje o przyczynie — okno edycji kanału zostało zamknięte, kanał jest rozłączony lub wystąpił inny błąd.

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

**Odpowiedź** (`200 OK`):

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

Jeśli kanał nie zaakceptuje edycji, otrzymasz zamiast tego `409` i nic nie zostanie zmienione:

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

Wiadomość, która została już usunięta, kanał, który w ogóle nie obsługuje edycji, oraz wiadomość, która jest zbyt stara dla danego kanału, zwracają `400` — żądanie nigdy nie dociera do kanału.

### Usuń jedną wiadomość

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

Usuwa wiadomość z Twojej konwersacji i, tam gdzie kanał na to pozwala, wycofuje również kopię kontaktu. Brak treści żądania.

To zawsze zwraca `200`, gdy wiadomość istniała, nawet jeśli kopia kontaktu nie mogła zostać wycofana — Twoja kopia **została** usunięta, więc błąd byłby mylący. Przeczytaj trzy pola w odpowiedzi, aby poinformować użytkownika, co faktycznie się stało:

| Pole | Opis |
|---|---|
| `revoke_supported` | Czy ten kanał w ogóle może wycofywać wiadomości. |
| `revoked` | Czy kopia na urządzeniu kontaktu została usunięta. |
| `revoke_reason` | Dlaczego nie została usunięta, gdy `revoked` ma wartość `false` — na przykład `revoke_window_closed` lub `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"])
```

**Odpowiedź** (`200 OK`):

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

> Usunięte wiadomości nie są usuwane z historii konwersacji. Pozostają w `GET /contacts/{contactId}/messages` z `is_deleted: true` oraz pustym `body` i `media_url`.

### Usuwanie kilku wiadomości jednocześnie

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

Czyści partię wiadomości tylko po Twojej stronie. Treści i załączniki są usuwane, ale **nic nie jest wycofywane na urządzeniu kontaktu** — aby również wycofać wiadomość, usuń ją pojedynczo za pomocą powyższego punktu końcowego dla pojedynczej wiadomości.

| Pole | Wymagane | Opis |
|---|---|---|
| `message_ids` | Tak | Niepusta tablica identyfikatorów wiadomości, do 500 na żądanie. `messageIds` jest akceptowane jako 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"])
```

**Odpowiedź** (`200 OK`):

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

### Reagowanie na wiadomość

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

Dodaje Twoją własną reakcję emoji do wiadomości lub cofa ją poprzez wysłanie pustego ciągu znaków. Reakcje kontaktu nigdy nie są zmieniane.

| Pole | Wymagane | Opis |
|---|---|---|
| `emoji` | Tak | Emoji, którym chcesz zareagować, lub `""`, aby usunąć swoją reakcję. Musi to być pojedynczy ciąg znaków bez spacji, maksymalnie 16 znaków. |

Podobnie jak w przypadku edycji, operacja ta kończy się niepowodzeniem zamiast wyświetlania reakcji, której kontakt nigdy nie otrzymał, a informacja o błędzie wskazuje, czy warto ponowić próbę:

- `422` — wiadomość nigdy nie może zostać dostarczona w tej konwersacji: kanał nie obsługuje reakcji, wiadomość nie ma identyfikatora po stronie kanału lub emoji znajduje się poza zestawem dozwolonym przez dany kanał.
- `409` — kanał był chwilowo nieosiągalny. Ponowna próba może zadziałać.

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

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

Tablica `reactions` to pełny zestaw reakcji znajdujących się obecnie na wiadomości, zarówno Twoich, jak i kontaktu. W przypadku `409` lub `422` jest ona zwracana bez zmian, więc klient renderujący bezpośrednio na jej podstawie nigdy nie wyświetli reakcji, która nie została dostarczona.

### Ocenianie lub oznaczanie wiadomości gwiazdką

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

Ocenia wiadomość kciukiem w górę lub w dół i/lub oznacza ją gwiazdką jako ważną. Jest to tylko ewidencja po Twojej stronie — nic nie jest wysyłane do kontaktu.

| Pole | Wymagane | Opis |
|---|---|---|
| `score` | Nie | `1` łapka w górę, `-1` łapka w dół, `0` usuwa ocenę. |
| `is_important` | Nie | `true` oznacza wiadomość gwiazdką, `false` usuwa gwiazdkę. Musi być wartością logiczną, a nie ciągiem znaków `"true"`. |

Wyślij przynajmniej jedno z tych dwóch, w przeciwnym razie otrzymasz `400`. Zapisywane jest tylko to, co wyślesz, więc oznaczenie wiadomości gwiazdką nigdy nie usuwa jej oceny i odwrotnie — a odpowiedź odzwierciedla tylko te pola, które zostały wysłane.

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

**Odpowiedź** (`200 OK`):

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

---

## Oznaczanie wiadomości jako przeczytane

Możesz wyczyścić stan nieprzeczytanych wiadomości dla konkretnych wiadomości lub dla całej konwersacji.

### Oznaczanie konkretnych wiadomości jako przeczytane

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

Przekaż identyfikatory wiadomości, które mają zostać oznaczone jako przeczytane.

| Pole | Wymagane | Opis |
|---|---|---|
| `message_ids` | Tak | Niepusta tablica identyfikatorów wiadomości (do 500 na żądanie). |

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

**Odpowiedź** (`200 OK`):

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

### Oznaczanie całego czatu jako przeczytanego

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

Czyści wskaźnik nieprzeczytanych wiadomości dla całej konwersacji kontaktu w skrzynce odbiorczej. Treść żądania nie jest wymagana.

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

**Odpowiedź** (`200 OK`):

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

### Oznacz cały czat jako nieprzeczytany

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

Przywraca plakietkę nieprzeczytanej wiadomości w konwersacji — przydatne, gdy ktoś z Twojego zespołu otworzył czat, ale przekazuje go z powrotem. Treść żądania nie jest wymagana.

Jest to flaga dotycząca tylko skrzynki odbiorczej: **nie** zmienia ona czasu ostatniego odczytania konwersacji, więc potwierdzenie odczytania nie jest wysyłane do kontaktu w kanałach, które je obsługują.

**cURL**

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

**Odpowiedź** (`200 OK`):

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

---

## Eksportuj konwersację

Eksporty zapewniają całą konwersację w formie czytelnego zapisu, zamiast przeglądania wiadomości strona po stronie. Każdy punkt końcowy eksportu akceptuje `filter` o wartości `all` (domyślnie), `text`, `media` lub `tool_use`, dopasowując się do filtra na liście wiadomości.

### Eksportuj czat jednego kontaktu

`GET /chat-exports/{contactId}`

| Parametr zapytania | Wymagane | Opis |
|---|---|---|
| `format` | Nie | `txt` (domyślnie) zwraca link do pobrania zapisu w formacie zwykłego tekstu. `json` zwraca wiadomości jako ustrukturyzowane dane w odpowiedzi. |
| `filter` | Nie | `all` (domyślnie), `text`, `media` lub `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"])
```

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

Przy `format=txt` (wartość domyślna), `data` jest zamiast tego linkiem do pobrania wygenerowanego pliku zapisu:

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

> **Link do pobrania jest krótkotrwały.** Pobierz plik zaraz po otrzymaniu linku, zamiast go przechowywać — poproś o nowy eksport, gdy ponownie będziesz potrzebować zapisu.

### Eksportuj wszystkie ostatnie konwersacje

`GET /chat-exports/recent`

Eksportuje konwersacje wszystkich kontaktów, które były aktywne w ciągu ostatnich X godzin, za pomocą jednego wywołania.

| Parametr zapytania | Wymagany | Opis |
|---|---|---|
| `hours` | Tak | Liczba godzin aktywności, które mają zostać uwzględnione. Musi być dodatnią liczbą całkowitą. |
| `format` | Nie | `json` (domyślnie) zwraca jeden wpis na kontakt. `txt` zwraca pojedynczy plik tekstowy do pobrania zawierający wszystkie konwersacje. |
| `limit` | Nie | Maksymalna liczba kontaktów do wyeksportowania. Domyślnie `50`, maksymalnie `100`. |
| `filter` | Nie | `all` (domyślnie), `text`, `media` lub `tool_use`. |

**cURL**

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

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

W przypadku `format=txt` odpowiedzią jest sam plik tekstowy, wysyłany jako plik do pobrania zamiast JSON.

> To wywołanie pobiera pełną historię każdego pasującego kontaktu, więc należy zachować umiar w wartościach `hours` i `limit` w przypadku bardzo aktywnych kont.

### Wyślij transkrypcję do kontaktu e-mailem

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

Wysyła kontaktowi jego własną transkrypcję konwersacji drogą mailową — jest to przepływ „wyślij mi ten czat na e-mail”, obsługiwany z poziomu Twojego własnego systemu.

| Pole | Wymagane | Opis |
|---|---|---|
| `recipient_email` | Nie | Adres, na który wysłać wiadomość. Domyślnie jest to adres e-mail zapisany w danych kontaktu. |
| `via` | Nie | `auto` (domyślnie) wybiera najlepszą ścieżkę, `transactional` wysyła wiadomość jako e-mail systemowy, `email_channel` wysyła wiadomość z Twojego połączonego kanału e-mail. |
| `note` | Nie | Krótka wiadomość od Ciebie wyświetlana nad transkrypcją. Maksymalnie 1000 znaków. |

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

**Odpowiedź** (`200 OK`):

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

`omittedCount` informuje, ile najstarszych wiadomości zostało pominiętych, aby zachować odpowiednią długość wiadomości e-mail. Wartość `200` oznacza, że transkrypcja została utworzona i dodana do kolejki wysyłania, a nie że dotarła już do skrzynki odbiorczej.

---

## Wstrzymywanie lub wznawianie działania AI dla jednego kontaktu

`PUT /contacts/{contactId}`

Ustaw `is_bot_active` na `false`, aby zatrzymać odpowiadanie AI jednemu kontaktowi, i z powrotem na `true`, aby przekazać mu konwersację. Jest to przełącznik przejęcia, którego potrzebujesz, gdy człowiek włącza się do rozmowy: wiadomości wychodzące wysyłane przez API są nadal dostarczane, gdy bot jest wstrzymany.

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

**Odpowiedź**

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

**Wstrzymywanie jako część odpowiedzi**

Jeśli człowiek przejmuje rozmowę, wysyłając odpowiedź, możesz wstrzymać bota w tym samym żądaniu, zamiast wykonywać drugie wywołanie. `POST /contacts/{contactId}/send-message` akceptuje dwie opcjonalne flagi:

| Pole | Opis |
|---|---|
| `pauseBot` | `true` wstrzymuje AI dla tego kontaktu w momencie wysłania wiadomości. |
| `clearIncompleteReply` | `true` odrzuca niedokończoną odpowiedź bota, aby nie została wznowiona później. |

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

Odpowiedź zawiera `"botPaused": true`, gdy wstrzymanie zostało zastosowane.

> Oznaczenie kontaktu jako prywatnego za pomocą [`POST /contacts/bulk-flag`](contacts.md) również wstrzymuje dla niego bota. Zobacz [Kontakty](contacts.md), aby uzyskać pełną listę pól.

---

## Budowanie własnej skrzynki odbiorczej

Wszystko, czego potrzebuje skrzynka odbiorcza, znajduje się na tej stronie oraz w [Kontaktach](contacts.md):

| Co chcesz zrobić | Punkt końcowy |
|---|---|
| Wyświetl listę konwersacji | `GET /contacts` |
| Odczytaj konwersację | `GET /contacts/{contactId}/messages` |
| Wyświetl listę sesji czatu kontaktu | `GET /chat-sessions/{contactId}` |
| Zobacz, co wpłynęło ostatnio | `GET /chat-sessions/recent` |
| Odczytaj jedną sesję czatu | `GET /contacts/{contactId}/chat-sessions/{sessionId}/messages` |
| Wyślij ręczną odpowiedź | `POST /contacts/{contactId}/send-message` |
| Popraw wysłaną przed chwilą odpowiedź | `POST /contacts/{contactId}/messages/{messageId}/edit` |
| Usuń wiadomość | `DELETE /contacts/{contactId}/messages/{messageId}` |
| Wyczyść kilka wiadomości | `POST /contacts/{contactId}/messages/bulk-delete` |
| Zareaguj za pomocą emoji | `POST /contacts/{contactId}/messages/{messageId}/react` |
| Oceń lub oznacz wiadomość gwiazdką | `PATCH /contacts/{contactId}/messages/{messageId}` |
| Oznacz jako przeczytane | `POST /contacts/{contactId}/mark-read` |
| Przekaż czat z powrotem do zespołu | `POST /contacts/{contactId}/mark-unread` |
| Eksportuj transkrypcję | `GET /chat-exports/{contactId}` |
| Wstrzymaj lub wznów działanie AI | `PUT /contacts/{contactId}` za pomocą `is_bot_active` |

Aby otrzymywać aktualizacje na żywo, zasubskrybuj zdarzenia `New Message`, `Replies`, `Human Alerted` oraz `Chat Concluded` za pomocą [Webhooks](webhooks.md), zamiast odpytywać to API w pętli czasowej.

---

## Błędy API wiadomości

Punkty końcowe wiadomości zwracają standardową kopertę błędu:

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

| Status | Kiedy występuje w punkcie końcowym wiadomości |
|---|---|
| `400` | Brakuje wymaganego pola lub parametr jest nieprawidłowy (błędne `limit`, `hours`, `filter`, `direction`, `status`, pusta lub przekraczająca 500 elementów tablica `message_ids`, nieprawidłowe `cursor`, pusta lub zbyt długa edycja `body`, `score` poza `-1`/`0`/`1` lub emoji ze spacjami albo dłuższe niż 16 znaków). Zwracane również, gdy wiadomości nie można w ogóle edytować — została usunięta, jej kanał nie obsługuje edycji lub minął czas na edycję w tym kanale. |
| `404` | Nie znaleziono kontaktu, sesji czatu lub jednego z podanych identyfikatorów wiadomości. |
| `409` | Kanał nie może obecnie przyjąć zmiany. Nic nie zostało zapisane: w przypadku edycji `edit_reason` wyjaśnia dlaczego; w przypadku reakcji kanał był chwilowo nieosiągalny i ponowienie próby może zadziałać. |
| `422` | Kontakt nie może otrzymywać wiadomości wychodzących (tryb nie przeszkadzać, prywatny lub nieobsługiwany kanał) albo reakcja nigdy nie może zostać dostarczona w tej konwersacji (`reaction_reason` wskazuje przyczynę). |

Wspólne kody, które może zwrócić każdy punkt końcowy — `401`, `403` (Twój plan nie obejmuje dostępu do API), `429` (limit szybkości) oraz `500` — zostały wymienione wraz ze wskazówkami dotyczącymi ponawiania prób w sekcji [Błędy i stronicowanie](errors-and-pagination.md).

---

## Następne kroki

- [Webhooks](webhooks.md) — otrzymuj powiadomienia o statusie dostarczenia zamiast odpytywać o nie.
- [Kontakty](contacts.md) — twórz i wyszukuj kontakty, do których wysyłasz wiadomości.
- [Spotkania](appointments.md) — rezerwuj i zarządzaj spotkaniami dla swoich kontaktów.
