
# Mesaje și conversații

API-ul de mesaje vă permite să trimiteți un mesaj oricărui contact, să citiți o conversație, să corectați sau să ștergeți un mesaj deja trimis, să reacționați la acesta, să extrageți un fir complet de sesiune de chat, să exportați o transcriere și să marcați chat-urile ca citite sau necitite — totul fără a deschide inbox-ul.

Toate căile de pe această pagină sunt relative la URL-ul de bază `https://api.youraiconnector.com/v1`. Fiecare cerere necesită cheia dvs. API — consultați [Autentificare](authentication.md) pentru lista completă a modalităților de trimitere a acesteia. Exemplele de mai jos utilizează antetul `X-API-Key`, un exemplu cURL arătând și forma de interogare `?apiKey=`.

> **Cum funcționează livrarea:** Trimiterea unui mesaj **nu** așteaptă ca acesta să ajungă la destinație. API-ul acceptă mesajul dvs., returnează imediat un ID de mesaj și apoi îl livrează în fundal pe canalul contactului (WhatsApp, SMS, Instagram etc.). Pentru a urmări dacă un mesaj a fost livrat sau citit efectiv, ascultați actualizările de stare cu [Webhooks](webhooks.md) — nu interogați (poll). Răspunsul de trimitere confirmă doar că mesajul a fost acceptat.

---

## Trimiteți un mesaj

Există două modalități de a trimite. Alegeți-o pe cea care se potrivește modului în care identificați deja contactul:

- **Trimitere prin ID-ul contactului** — cunoașteți deja ID-ul contactului (de exemplu, ați creat contactul prin API sau l-ați obținut dintr-un webhook). Utilizați `POST /contacts/{contactId}/send-message`.
- **Trimitere prin identitatea contactului** — cunoașteți numărul de telefon al contactului, ID-ul de Instagram etc., dar nu și ID-ul lor intern. Utilizați `POST /contacts/send` și lăsați platforma să găsească contactul potrivit.

Ambele pun mesajul în coadă în același mod și îl livrează pe canalul pe care se află contactul. Nu alegeți un transport — platforma direcționează contactele WhatsApp prin WhatsApp, contactele SMS prin SMS și așa mai departe.

### Trimitere prin ID-ul contactului

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

| Câmp | Obligatoriu | Descriere |
|---|---|---|
| `body` | Da | Textul mesajului de trimis. |
| `mediaUrl` | Nu | URL-ul unui fișier media (imagine, document etc.) de atașat. |
| `mediaContentType` | Nu | Tipul MIME al fișierului media atașat, de ex. `image/jpeg`. |
| `pauseBot` | Nu | `true` suspendă AI-ul pentru acest contact în momentul trimiterii mesajului — pentru preluarea de către un operator uman. Consultați [Suspendarea sau reluarea AI-ului](#pause-or-resume-the-ai-for-one-contact). |
| `clearIncompleteReply` | Nu | `true` elimină un răspuns parțial al botului, astfel încât acesta să nu fie reluat după mesajul dumneavoastră. |

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

**Răspuns** (`200 OK`):

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

### Trimitere prin identitatea contactului

`POST /contacts/send`

Utilizați acest lucru atunci când nu aveți ID-ul intern al contactului. Furnizați `body` mesajului plus **fie** un `contact_id`, **fie** un `channel` împreună cu câmpul de identitate care corespunde acelui canal.

| Câmp | Obligatoriu | Descriere |
|---|---|---|
| `body` | Da | Textul mesajului de trimis. |
| `contact_id` | Nu | ID-ul unui contact existent. Când este setat, câmpurile de identitate de mai jos nu sunt necesare. |
| `channel` | Nu | Canalul prin care se trimite. Obligatoriu când `contact_id` nu este furnizat. Unul dintre cele 14 canale de ieșire disponibile: `whatsapp`, `whatsapp_web`, `sms`, `instagram`, `instagram_private`, `messenger`, `telegram`, `chat-widget`, `custom`, `email`, `line`, `imessage`, `linkedin`, `viber`. |
| `phone_number` | Nu | Numărul de telefon al contactului în format internațional. Utilizat cu `whatsapp`, `whatsapp_web` și `sms`. |
| `instagram_id` | Nu | ID-ul de utilizator Instagram al contactului. Utilizat cu `instagram`. |
| `messenger_id` | Nu | ID-ul de utilizator Messenger al contactului. Utilizat cu `messenger`. |
| `telegram_user_id` | Nu | ID-ul de utilizator Telegram al contactului. Utilizat cu `telegram`. |
| `media_url` | Nu | URL-ul unui fișier media de atașat. |
| `media_content_type` | Nu | Tipul MIME al fișierului media atașat, de ex. `image/jpeg`. |

**Ce canale pot fi identificate prin identitate.** Doar șase din cele 14 acceptă un câmp de identitate în locul unui `contact_id`: `whatsapp`, `whatsapp_web` și `sms` sunt căutate prin `phone_number`, `instagram` prin `instagram_id`, `messenger` prin `messenger_id`, și `telegram` prin `telegram_user_id`. Celelalte opt — `instagram_private`, `chat-widget`, `custom`, `email`, `line`, `imessage`, `linkedin` și `viber` — nu au nicio identitate publică de căutat, deci trimiterea prin acele canale necesită `contact_id`; transmiterea doar a `channel` returnează un `400` care vă informează că `contact_id` este necesar.

**cURL** (folosind forma de interogare `?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"])
```

**Răspuns** (`201 Created`):

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

> **De ce un mesaj ar putea fi respins:** Un contact care are activat modul „nu deranjați” sau modul privat nu poate primi mesaje de ieșire — cererea eșuează cu un `422`. Dacă niciun contact nu corespunde ID-ului sau identității furnizate, veți primi un `404`.

---

## Listarea mesajelor unui contact

`GET /contacts/{contactId}/messages`

Returnează mesajele unui contact, cele mai noi primele, cu paginare bazată pe cursor.

| Parametru de interogare | Obligatoriu | Descriere |
|---|---|---|
| `limit` | Nu | Dimensiunea paginii. Implicit `50`, maxim `100`. |
| `cursor` | Nu | Valoarea `next_cursor` dintr-un răspuns anterior. Returnează mesaje mai vechi decât cursorul. |
| `filter` | Nu | Filtrare după tipul de conținut: `all` (implicit), `text`, `media` sau `tool_use`. |
| `direction` | Nu | Filtrare după direcție: `all` (implicit), `inbound` (primit de la contact) sau `outbound` (trimis de dvs.). |

> **Notă despre filtrare și paginare:** Filtrele `filter` și `direction` sunt aplicate fiecărei pagini după ce este citită, deci o pagină filtrată poate conține mai puține elemente decât `limit`. `next_cursor` avansează în continuare prin întreaga conversație, așa că continuați paginarea până când `next_cursor` este `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"])
```

**Răspuns** (`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"
}
```

### Câmpurile mesajului

| Câmp | Descriere |
|---|---|
| `id` | ID-ul unic al mesajului. |
| `body` | Conținutul text al mesajului. |
| `direction` | `inbound` (primit de la contact) sau `outbound` (trimis de contul dvs.). |
| `channel` | Canalul pe care a fost trimis sau primit mesajul (de ex. `whatsapp`, `sms`, `instagram`). |
| `status` | Starea curentă a livrării, de ex. `Created`, `sent`, `delivered`, `read`, `failed`. |
| `type` | Tipul mesajului. Mesajele text simplu au tipul `null`; activitatea instrumentelor de asistență automatizată este marcată cu `tool_use`. |
| `timestamp` | Ora ISO 8601 la care a fost creat mesajul. |
| `media_url` | URL-ul unui fișier media atașat, dacă există. |
| `media_content_type` | Tipul MIME al fișierului media atașat, dacă există. |
| `bot_reply` | `true` când mesajul a fost generat de asistentul AI. |
| `score` | Evaluarea dvs. pentru mesaj: `1` deget în sus, `-1` deget în jos, `0` când nu a fost evaluat. Consultați [Evaluați sau marcați cu stea un mesaj](#rate-or-star-a-message). |
| `is_important` | `true` când mesajul a fost marcat cu stea. |
| `is_deleted` | `true` când mesajul a fost șters. Mesajele șterse rămân în listă, dar `body` și `media_url` sunt goale. |
| `reactions` | Reacții emoji la mesaj, din ambele părți. Întotdeauna un tablou — gol când nu există niciuna. Fiecare intrare are `emoji`, `from_phone_number`, `from_me` (`true` când reacția este a dvs.) și `reacted_at`. |

---

## Listarea sesiunilor de chat

O sesiune de chat este o fereastră de conversație cu un contact: se deschide când acesta începe să vorbească și se închide când conversația este încheiată. Sesiunile reprezintă modul în care puteți împărți un istoric lung în conversații lizibile, în loc de o listă nesfârșită.

### Sesiuni recente pentru toate contactele

`GET /chat-sessions/recent`

Returnează sesiunile care au început în ultimele X ore, cele mai noi primele, pentru fiecare contact din cont.

| Parametru de interogare | Obligatoriu | Descriere |
|---|---|---|
| `hours` | Da | Câte ore să fie luate în considerare. Trebuie să fie un număr întreg pozitiv. |
| `status` | Nu | Returnează doar sesiunile cu această stare: `ChatSessionOpened` sau `ChatSessionClosed`. |
| `limit` | Nu | Numărul maxim de sesiuni de returnat. Implicit `100`, maxim `100`. |
| `includeMessages` | Nu | `true` adaugă un tablou `messages` la fiecare sesiune. Dezactivat implicit deoarece mărește considerabil răspunsul. |

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

**Răspuns** (`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"
      }
    ]
  }
}
```

### Toate sesiunile pentru un singur contact

`GET /chat-sessions/{contactId}`

Returnează fiecare sesiune de chat pentru un singur contact. Aceiași parametri `status`, `limit` și `includeMessages` ca mai sus — `hours` nu se aplică aici.

**cURL**

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

**Răspuns** (`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"
      }
    ]
  }
}
```

> **Numele câmpurilor ID-ului de sesiune diferă între cele două endpoint-uri.** Lista sesiunilor recente îl numește `session_id` (conține și detaliile contactului, deoarece sesiunile provin de la mai multe contacte); lista per-contact îl numește `id`. Oricare dintre aceste valori este cea pe care o transmiteți ca `{sessionId}` atunci când preluați firul complet de mai jos.

Când `includeMessages=true`, fiecare sesiune primește un tablou `messages` ale cărui intrări conțin `id`, `body`, `direction`, `timestamp`, `type`, `channel` și `status`.

---

## Preluarea unui fir de conversație

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

O sesiune de chat grupează mesajele unui contact într-o singură fereastră de conversație. Acest endpoint returnează întregul fir al unei singure sesiuni, **de la cel mai vechi la cel mai nou**, împreună cu metadatele sesiunii. Puteți găsi ID-urile sesiunilor pentru un contact prin intermediul endpoint-urilor pentru sesiuni de chat.

**cURL**

```bash
curl "https://api.youraiconnector.com/v1/contacts/contact123/chat-sessions/session456/messages" \
  -H "X-API-Key: YOUR_API_KEY"
```

**JavaScript**

```javascript
const res = await fetch(
  "https://api.youraiconnector.com/v1/contacts/contact123/chat-sessions/session456/messages",
  { headers: { "X-API-Key": "YOUR_API_KEY" } }
);
const data = await res.json();
console.log(data.session, data.messages);
```

**Python**

```python
import requests

res = requests.get(
    "https://api.youraiconnector.com/v1/contacts/contact123/chat-sessions/session456/messages",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
data = res.json()
print(data["session"], data["messages"])
```

**Răspuns** (`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
    }
  ]
}
```

Obiectul `session` raportează `status` (`ChatSessionOpened` cât timp este activ, `ChatSessionClosed` odată încheiat), `start_date_time`, `end_date_time` și un `tag` ușor de citit de către oameni. Matricea `messages` folosește aceleași [câmpuri de mesaj](#message-fields) ca și endpoint-ul de listare.

---

## Editarea, ștergerea și reacționarea la mesaje

Aceste endpoint-uri modifică un mesaj după ce a fost trimis. Două dintre ele interacționează atât cu canalul contactului, cât și cu propria dvs. copie, așa că citiți introducerea secțiunii înainte de a le implementa — ceea ce este posibil depinde în întregime de canalul pe care se desfășoară conversația.

**Ce permite fiecare canal**

| Acțiune | Canale care pot modifica copia contactului | Limită de timp |
|---|---|---|
| Editarea unui mesaj trimis | Chat widget, WhatsApp Web, Telegram, LinkedIn | Fără limită pe chat widget, 15 minute pe WhatsApp Web, 48 de ore pe Telegram, 60 de minute pe LinkedIn |
| Ștergere pentru toată lumea | Chat widget, WhatsApp Web, Telegram, LinkedIn | 60 de minute pe LinkedIn; celelalte nu au o limită publicată |
| Reacție cu emoji | WhatsApp Web, Telegram | Fără limită |

Pe toate celelalte canale — WhatsApp Business API, SMS, Instagram, Messenger, e-mail, LINE, canale personalizate — o ștergere elimină mesajul din inbox-ul tău, dar contactul își păstrează copia, iar editarea sau reacționarea nu sunt posibile deloc.

### Editarea unui mesaj

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

Rescrie un mesaj pe care l-ai trimis deja, pe dispozitivul contactului și în copia ta.

| Câmp | Obligatoriu | Descriere |
|---|---|---|
| `body` | Da | Noul text al mesajului. Nu trebuie să fie gol și poate avea maximum 4096 de caractere. |

Spre deosebire de ștergere, aceasta **eșuează zgomotos** atunci când canalul refuză: primești un `409`, iar copia ta rămâne exact așa cum o are contactul, deoarece afișarea unei editări pe care aceștia nu au primit-o niciodată ar duce la o neconcordanță între cele două părți. Câmpul `edit_reason` îți spune de ce — fereastra de editare a canalului s-a închis, canalul este deconectat sau a apărut o altă eroare.

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

**Răspuns** (`200 OK`):

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

Dacă canalul nu acceptă editarea, primești în schimb un `409` și nimic nu a fost modificat:

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

Un mesaj care a fost deja șters, un canal care nu poate edita deloc și un mesaj care este prea vechi pentru canalul său returnează toate `400` — cererea nu ajunge niciodată la canal.

### Ștergerea unui mesaj

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

Elimină mesajul din conversația ta și, acolo unde canalul permite, retrage și copia contactului. Fără corp de cerere.

Aceasta răspunde întotdeauna cu `200` atunci când mesajul a existat, chiar dacă copia contactului nu a putut fi retrasă — copia ta **este** ștearsă, deci o eroare ar fi înșelătoare. Citește cele trei câmpuri din răspuns pentru a-i spune utilizatorului ce s-a întâmplat de fapt:

| Câmp | Descriere |
|---|---|
| `revoke_supported` | Dacă acest canal poate retrage mesaje în general. |
| `revoked` | Dacă copia de pe dispozitivul contactului a fost eliminată. |
| `revoke_reason` | De ce nu a fost eliminată, atunci când `revoked` este `false` — de exemplu `revoke_window_closed` sau `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"])
```

**Răspuns** (`200 OK`):

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

> Mesajele șterse nu sunt eliminate din istoricul conversației. Acestea rămân în `GET /contacts/{contactId}/messages` cu `is_deleted: true` și un `body` gol și `media_url`.

### Ștergeți mai multe mesaje simultan

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

Șterge un lot de mesaje doar din partea ta. Conținutul și atașamentele sunt golite, dar **nimic nu este retras de pe dispozitivul contactului** — pentru a retrage și un mesaj, ștergeți-l pe rând folosind endpoint-ul pentru un singur mesaj de mai sus.

| Câmp | Obligatoriu | Descriere |
|---|---|---|
| `message_ids` | Da | O matrice nevidă de ID-uri de mesaje, până la 500 per cerere. `messageIds` este acceptat ca 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"])
```

**Răspuns** (`200 OK`):

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

### Reacționați la un mesaj

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

Adaugă propria reacție emoji la un mesaj sau o retrage prin trimiterea unui șir gol. Reacțiile contactului nu sunt niciodată modificate.

| Câmp | Obligatoriu | Descriere |
|---|---|---|
| `emoji` | Da | Emoji-ul cu care doriți să reacționați sau `""` pentru a elimina reacția. Trebuie să fie un singur șir fără spații, de maximum 16 caractere. |

La fel ca editarea, aceasta eșuează în loc să afișeze o reacție pe care contactul nu a primit-o niciodată, iar eșecul vă indică dacă merită să reîncercați:

- `422` — nu poate fi livrat niciodată în această conversație: canalul nu acceptă reacții, mesajul nu are un ID pe partea canalului sau emoji-ul este în afara setului permis de acel canal.
- `409` — canalul a fost temporar inaccesibil. O reîncercare ar putea funcționa.

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

**Răspuns** (`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"
    }
  ]
}
```

Matricea `reactions` reprezintă setul complet de reacții existente acum pe mesaj, atât ale tale, cât și ale contactului. La un `409` sau `422`, aceasta este returnată neschimbată, astfel încât un client care randează direct din ea nu va afișa niciodată o reacție care nu a fost livrată.

### Evaluați sau marcați un mesaj cu stea

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

Evaluează un mesaj cu degetul în sus sau în jos și/sau îl marchează ca important. Aceasta este o evidență doar pe partea ta — nimic nu este trimis contactului.

| Câmp | Obligatoriu | Descriere |
|---|---|---|
| `score` | Nu | `1` deget în sus, `-1` deget în jos, `0` șterge evaluarea. |
| `is_important` | Nu | `true` adaugă mesajul la favorite, `false` elimină mesajul de la favorite. Trebuie să fie un boolean real, nu șirul `"true"`. |

Trimite cel puțin unul dintre cele două, altfel vei primi o eroare `400`. Doar ceea ce trimiți este scris, deci marcarea unui mesaj ca favorit nu îi șterge niciodată evaluarea și invers — iar răspunsul returnează doar câmpurile pe care le-ai trimis.

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

**Răspuns** (`200 OK`):

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

---

## Marcarea mesajelor ca citite

Puteți șterge starea de necitit fie pentru mesaje specifice, fie pentru întreaga conversație.

### Marcarea mesajelor specifice ca citite

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

Transmiteți ID-urile mesajelor care trebuie marcate ca citite.

| Câmp | Obligatoriu | Descriere |
|---|---|---|
| `message_ids` | Da | O matrice nevidă de ID-uri de mesaje (până la 500 per cerere). |

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

**Răspuns** (`200 OK`):

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

### Marcarea întregului chat ca citit

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

Șterge insigna de necitit pentru întreaga conversație a contactului din inbox. Nu este necesar niciun corp de cerere.

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

**Răspuns** (`200 OK`):

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

### Marchează întreaga conversație ca necitită

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

Pune din nou insigna de necitit pe conversație — util atunci când cineva din echipa ta a deschis un chat, dar îl predă înapoi. Nu este necesar un corp al cererii.

Acesta este un indicator valabil doar pentru inbox: **nu** modifică momentul în care conversația a fost citită ultima dată, deci nu este trimisă nicio confirmare de citire către contact pe canalele care le suportă.

**cURL**

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

**Răspuns** (`200 OK`):

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

---

## Exportă o conversație

Exporturile îți oferă o conversație întreagă sub formă de transcriere lizibilă, în loc să parcurgi mesajele pagină cu pagină. Fiecare endpoint de export acceptă un `filter` de tip `all` (implicit), `text`, `media` sau `tool_use`, corespunzător filtrului din lista de mesaje.

### Exportă chat-ul unui singur contact

`GET /chat-exports/{contactId}`

| Parametru de interogare | Obligatoriu | Descriere |
|---|---|---|
| `format` | Nu | `txt` (implicit) returnează un link de descărcare către o transcriere în text simplu. `json` returnează mesajele ca date structurate în răspuns. |
| `filter` | Nu | `all` (implicit), `text`, `media` sau `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"])
```

**Răspuns cu `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
      }
    ]
  }
}
```

Cu `format=txt` (implicit), `data` este în schimb un link de descărcare către fișierul de transcriere generat:

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

> **Linkul de descărcare are o durată scurtă de viață.** Descarcă fișierul imediat ce primești linkul, în loc să îl stochezi — solicită un export nou atunci când ai din nou nevoie de transcriere.

### Exportă toate conversațiile recente

`GET /chat-exports/recent`

Exportă conversațiile tuturor contactelor care au fost active în ultimele X ore, într-un singur apel.

| Parametru interogare | Obligatoriu | Descriere |
|---|---|---|
| `hours` | Da | Câte ore de activitate să fie luate în considerare. Trebuie să fie un număr întreg pozitiv. |
| `format` | Nu | `json` (implicit) returnează o intrare per contact. `txt` returnează un singur fișier text descărcabil care conține toate conversațiile. |
| `limit` | Nu | Numărul maxim de contacte de exportat. Implicit `50`, maxim `100`. |
| `filter` | Nu | `all` (implicit), `text`, `media` sau `tool_use`. |

**cURL**

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

**Răspuns** (`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..."
      }
    ]
  }
}
```

Cu `format=txt`, răspunsul este fișierul text propriu-zis, trimis ca descărcare în loc de JSON.

> Acest apel unic extrage istoricul complet al fiecărui contact corespondent, așa că păstrați `hours` și `limit` la valori moderate în conturile ocupate.

### Trimiteți o transcriere prin e-mail contactului

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

Trimite contactului propria transcriere a conversației prin e-mail — fluxul „trimite-mi acest chat prin e-mail”, gestionat din propriul sistem.

| Câmp | Obligatoriu | Descriere |
|---|---|---|
| `recipient_email` | Nu | Unde să fie trimis. Implicit este adresa de e-mail stocată a contactului. |
| `via` | Nu | `auto` (implicit) alege cea mai bună rută, `transactional` îl trimite ca e-mail de sistem, `email_channel` îl trimite de pe canalul de e-mail conectat. |
| `note` | Nu | Un scurt mesaj de la dumneavoastră afișat deasupra transcrierii. Până la 1000 de caractere. |

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

**Răspuns** (`200 OK`):

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

`omittedCount` vă spune câte dintre cele mai vechi mesaje au fost omise pentru a menține e-mailul la o lungime rezonabilă. Un `200` înseamnă că transcrierea a fost creată și pusă în coada de așteptare pentru trimitere, nu că a ajuns deja în căsuța poștală.

---

## Suspendarea sau reluarea AI-ului pentru un contact

`PUT /contacts/{contactId}`

Setați `is_bot_active` la `false` pentru a opri AI-ul din a răspunde unui contact și înapoi la `true` pentru a preda conversația. Acesta este comutatorul de preluare pe care îl doriți atunci când un operator uman intervine într-o conversație: mesajele trimise prin API sunt livrate în continuare în timp ce botul este suspendat.

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

**Răspuns**

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

**Suspendarea ca parte a răspunsului**

Dacă un operator uman preia conversația trimițând un răspuns, puteți suspenda botul în aceeași cerere, în loc să efectuați un al doilea apel. `POST /contacts/{contactId}/send-message` acceptă două opțiuni opționale:

| Câmp | Descriere |
|---|---|
| `pauseBot` | `true` suspendă AI-ul pentru acest contact în momentul trimiterii mesajului. |
| `clearIncompleteReply` | `true` elimină un răspuns parțial al botului, astfel încât acesta să nu fie reluat ulterior. |

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

Răspunsul include `"botPaused": true` atunci când suspendarea a fost aplicată.

> Marcarea unui contact ca privat cu [`POST /contacts/bulk-flag`](contacts.md) suspendă, de asemenea, botul pentru acesta. Consultați [Contacte](contacts.md) pentru lista completă a câmpurilor.

---

## Construirea propriei căsuțe de primire

Tot ce are nevoie o căsuță de primire se află pe această pagină și în [Contacte](contacts.md):

| Ce aveți nevoie | Endpoint |
|---|---|
| Listare conversații | `GET /contacts` |
| Citire conversație | `GET /contacts/{contactId}/messages` |
| Listare sesiuni de chat ale unui contact | `GET /chat-sessions/{contactId}` |
| Vizualizare mesaje recente | `GET /chat-sessions/recent` |
| Citire sesiune de chat | `GET /contacts/{contactId}/chat-sessions/{sessionId}/messages` |
| Trimitere răspuns manual | `POST /contacts/{contactId}/send-message` |
| Corectare răspuns trimis recent | `POST /contacts/{contactId}/messages/{messageId}/edit` |
| Eliminare mesaj | `DELETE /contacts/{contactId}/messages/{messageId}` |
| Ștergere mai multe mesaje | `POST /contacts/{contactId}/messages/bulk-delete` |
| Reacție cu emoji | `POST /contacts/{contactId}/messages/{messageId}/react` |
| Evaluare sau marcare cu stea a unui mesaj | `PATCH /contacts/{contactId}/messages/{messageId}` |
| Marcare ca citit | `POST /contacts/{contactId}/mark-read` |
| Redirecționare chat către echipă | `POST /contacts/{contactId}/mark-unread` |
| Exportare transcriere | `GET /chat-exports/{contactId}` |
| Pauză sau reluare AI | `PUT /contacts/{contactId}` cu `is_bot_active` |

Pentru actualizări în timp real, abonați-vă la evenimentele `New Message`, `Replies`, `Human Alerted` și `Chat Concluded` folosind [Webhooks](webhooks.md) în loc să interogați acest API la un anumit interval de timp.

---

## Erori API mesaje

Endpoint-urile de mesaje returnează plicul de eroare standard:

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

| Stare | Când se întâmplă pe un endpoint de mesaje |
|---|---|
| `400` | Un câmp obligatoriu lipsește sau un parametru este invalid (`limit`, `hours`, `filter`, `direction`, `status` incorect, un tablou `message_ids` gol sau de peste 500 de elemente, un `cursor` invalid, o editare `body` goală sau prea lungă, un `score` în afara `-1`/`0`/`1`, sau un emoji cu spații sau de peste 16 caractere). De asemenea, returnat atunci când un mesaj nu poate fi editat deloc — a fost șters, canalul său nu permite editarea sau a depășit fereastra de editare a acelui canal. |
| `404` | Contactul, sesiunea de chat sau unul dintre ID-urile de mesaj furnizate nu a fost găsit. |
| `409` | Canalul nu a putut accepta modificarea în acest moment. Nu s-a scris nimic: la o editare, `edit_reason` explică de ce; la o reacție, canalul a fost momentan inaccesibil și o reîncercare ar putea funcționa. |
| `422` | Contactul nu poate primi mesaje de ieșire (nu deranjați, privat sau un canal neacceptat), sau o reacție nu poate fi livrată niciodată în această conversație (`reaction_reason` indică care). |

Codurile partajate pe care orice endpoint le poate returna — `401`, `403` (planul dvs. nu include acces API), `429` (limită de rată) și `500` — sunt listate cu îndrumări pentru reîncercare în [Erori și Paginare](errors-and-pagination.md).

---

## Pașii următori

- [Webhooks](webhooks.md) — primiți actualizări privind starea livrării în loc să interogați.
- [Contacte](contacts.md) — creați și căutați contactele cărora le trimiteți mesaje.
- [Programări](appointments.md) — rezervați și gestionați programările pentru contactele dvs.
