
# Pesan & Percakapan

Messages API memungkinkan Anda mengirim pesan ke kontak mana pun, membaca kembali percakapan, mengoreksi atau menghapus pesan yang sudah Anda kirim, memberikan reaksi pada pesan, menarik utas sesi obrolan lengkap, mengekspor transkrip, dan menandai obrolan sebagai sudah atau belum dibaca — semuanya tanpa perlu membuka kotak masuk.

Semua jalur di halaman ini relatif terhadap URL dasar `https://api.youraiconnector.com/v1`. Setiap permintaan memerlukan kunci API Anda — lihat [Autentikasi](authentication.md) untuk daftar lengkap cara mengirimkannya. Contoh di bawah menggunakan header `X-API-Key`, dengan satu contoh cURL yang juga menunjukkan formulir kueri `?apiKey=`.

> **Cara kerja pengiriman:** Mengirim pesan **tidak** menunggu pesan tersebut sampai. API menerima pesan Anda, segera mengembalikan ID pesan, lalu mengirimkannya di latar belakang melalui saluran kontak (WhatsApp, SMS, Instagram, dan sebagainya). Untuk melacak apakah pesan benar-benar terkirim atau telah dibaca, dengarkan pembaruan status dengan [Webhook](webhooks.md) — jangan melakukan polling. Respons pengiriman hanya mengonfirmasi bahwa pesan telah diterima.

---

## Mengirim pesan

Ada dua cara untuk mengirim. Pilih yang paling sesuai dengan cara Anda mengidentifikasi kontak:

- **Kirim berdasarkan ID kontak** — Anda sudah mengetahui ID kontak tersebut (misalnya, Anda membuat kontak melalui API atau mendapatkannya dari webhook). Gunakan `POST /contacts/{contactId}/send-message`.
- **Kirim berdasarkan identitas kontak** — Anda mengetahui nomor telepon, ID Instagram, dll. milik kontak tersebut, tetapi bukan ID internal mereka. Gunakan `POST /contacts/send` dan biarkan platform menemukan kontak yang tepat.

Keduanya mengantrekan pesan dengan cara yang sama dan mengirimkannya melalui saluran apa pun yang digunakan kontak tersebut. Anda tidak perlu memilih transportasi — platform merutekan kontak WhatsApp melalui WhatsApp, kontak SMS melalui SMS, dan seterusnya.

### Kirim berdasarkan ID kontak

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

| Bidang | Wajib | Deskripsi |
|---|---|---|
| `body` | Ya | Teks pesan yang akan dikirim. |
| `mediaUrl` | Tidak | URL file media (gambar, dokumen, dll.) untuk dilampirkan. |
| `mediaContentType` | Tidak | Tipe MIME dari media yang dilampirkan, contohnya `image/jpeg`. |
| `pauseBot` | Tidak | `true` menjeda AI untuk kontak ini saat pesan dikirim — untuk pengambilalihan oleh manusia. Lihat [Jeda atau lanjutkan AI](#pause-or-resume-the-ai-for-one-contact). |
| `clearIncompleteReply` | Tidak | `true` membuang balasan bot yang setengah jadi agar tidak dilanjutkan setelah pesan Anda. |

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

**Respons** (`200 OK`):

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

### Kirim berdasarkan identitas kontak

`POST /contacts/send`

Gunakan ini jika Anda tidak memiliki ID internal kontak. Berikan `body` pesan ditambah **salah satu** dari `contact_id`, **atau** `channel` bersama dengan bidang identitas yang cocok dengan saluran tersebut.

| Bidang | Wajib | Deskripsi |
|---|---|---|
| `body` | Ya | Teks pesan yang akan dikirim. |
| `contact_id` | Tidak | ID kontak yang sudah ada. Jika diatur, bidang identitas di bawah tidak diperlukan. |
| `channel` | Tidak | Saluran untuk mengirim pesan. Wajib diisi jika `contact_id` tidak diberikan. Salah satu dari 14 saluran yang dapat mengirim pesan keluar: `whatsapp`, `whatsapp_web`, `sms`, `instagram`, `instagram_private`, `messenger`, `telegram`, `chat-widget`, `custom`, `email`, `line`, `imessage`, `linkedin`, `viber`. |
| `phone_number` | Tidak | Nomor telepon kontak dalam format internasional. Digunakan dengan `whatsapp`, `whatsapp_web`, dan `sms`. |
| `instagram_id` | Tidak | ID pengguna Instagram kontak. Digunakan dengan `instagram`. |
| `messenger_id` | Tidak | ID pengguna Messenger kontak. Digunakan dengan `messenger`. |
| `telegram_user_id` | Tidak | ID pengguna Telegram kontak. Digunakan dengan `telegram`. |
| `media_url` | Tidak | URL file media untuk dilampirkan. |
| `media_content_type` | Tidak | Tipe MIME dari media yang dilampirkan, contohnya `image/jpeg`. |

**Saluran mana yang dapat diselesaikan berdasarkan identitas.** Hanya enam dari 14 saluran yang menerima bidang identitas sebagai pengganti `contact_id`: `whatsapp`, `whatsapp_web` dan `sms` dicari berdasarkan `phone_number`, `instagram` berdasarkan `instagram_id`, `messenger` berdasarkan `messenger_id`, dan `telegram` berdasarkan `telegram_user_id`. Delapan saluran lainnya — `instagram_private`, `chat-widget`, `custom`, `email`, `line`, `imessage`, `linkedin` dan `viber` — tidak memiliki identitas publik untuk dicari, sehingga pengiriman pada saluran tersebut memerlukan `contact_id`; mengirim `channel` saja akan mengembalikan `400` yang memberi tahu Anda bahwa `contact_id` diperlukan.

**cURL** (menggunakan formulir kueri `?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"])
```

**Respons** (`201 Created`):

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

> **Mengapa pesan mungkin ditolak:** Kontak dengan mode jangan ganggu atau mode pribadi aktif tidak dapat menerima pesan keluar — permintaan akan gagal dengan `422`. Jika tidak ada kontak yang cocok dengan ID atau identitas yang Anda berikan, Anda akan mendapatkan `404`.

---

## Mencantumkan pesan kontak

`GET /contacts/{contactId}/messages`

Mengembalikan pesan kontak, yang terbaru terlebih dahulu, dengan penomoran halaman berbasis kursor.

| Parameter kueri | Wajib | Deskripsi |
|---|---|---|
| `limit` | Tidak | Ukuran halaman. Default `50`, maksimum `100`. |
| `cursor` | Tidak | Nilai `next_cursor` dari respons sebelumnya. Mengembalikan pesan yang lebih lama dari kursor. |
| `filter` | Tidak | Filter berdasarkan tipe konten: `all` (default), `text`, `media`, atau `tool_use`. |
| `direction` | Tidak | Filter berdasarkan arah: `all` (default), `inbound` (diterima dari kontak), atau `outbound` (dikirim oleh Anda). |

> **Catatan tentang pemfilteran dan penomoran halaman:** Filter `filter` dan `direction` diterapkan ke setiap halaman setelah dibaca, sehingga halaman yang difilter dapat berisi lebih sedikit item daripada `limit`. `next_cursor` tetap berlanjut melalui percakapan penuh, jadi terus lakukan penomoran halaman hingga `next_cursor` adalah `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"])
```

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

### Bidang pesan

| Bidang | Deskripsi |
|---|---|
| `id` | ID unik pesan. |
| `body` | Konten teks pesan. |
| `direction` | `inbound` (diterima dari kontak) atau `outbound` (dikirim oleh akun Anda). |
| `channel` | Saluran tempat pesan dikirim atau diterima (misalnya `whatsapp`, `sms`, `instagram`). |
| `status` | Status pengiriman saat ini, misalnya `Created`, `sent`, `delivered`, `read`, `failed`. |
| `type` | Jenis pesan. Pesan teks biasa memiliki jenis `null`; aktivitas alat asisten otomatis ditandai `tool_use`. |
| `timestamp` | Waktu ISO 8601 saat pesan dibuat. |
| `media_url` | URL file media yang dilampirkan, jika ada. |
| `media_content_type` | Jenis MIME media yang dilampirkan, jika ada. |
| `bot_reply` | `true` saat pesan dihasilkan oleh asisten AI. |
| `score` | Penilaian Anda terhadap pesan: `1` jempol ke atas, `-1` jempol ke bawah, `0` saat belum dinilai. Lihat [Beri nilai atau bintang pada pesan](#rate-or-star-a-message). |
| `is_important` | `true` saat pesan telah diberi bintang. |
| `is_deleted` | `true` saat pesan telah dihapus. Pesan yang dihapus tetap ada dalam daftar tetapi `body` dan `media_url`-nya kosong. |
| `reactions` | Reaksi emoji pada pesan, dari kedua belah pihak. Selalu berupa array — kosong jika tidak ada. Setiap entri memiliki `emoji`, `from_phone_number`, `from_me` (`true` saat reaksi tersebut adalah milik Anda) dan `reacted_at`. |

---

## Daftar sesi obrolan

Sesi obrolan adalah satu jendela percakapan dengan kontak: sesi terbuka saat mereka mulai berbicara dan ditutup saat percakapan selesai. Sesi adalah cara Anda membagi riwayat panjang menjadi percakapan yang dapat dibaca, alih-alih satu daftar yang tidak ada habisnya.

### Sesi terbaru di semua kontak

`GET /chat-sessions/recent`

Mengembalikan sesi yang dimulai dalam X jam terakhir, yang terbaru terlebih dahulu, di seluruh kontak pada akun tersebut.

| Parameter kueri | Wajib | Deskripsi |
|---|---|---|
| `hours` | Ya | Berapa jam untuk melihat ke belakang. Harus berupa bilangan bulat positif. |
| `status` | Tidak | Hanya kembalikan sesi dengan status ini: `ChatSessionOpened` atau `ChatSessionClosed`. |
| `limit` | Tidak | Jumlah maksimum sesi yang akan dikembalikan. Default `100`, maksimum `100`. |
| `includeMessages` | Tidak | `true` menambahkan array `messages` ke setiap sesi. Nonaktif secara default karena membuat respons menjadi jauh lebih besar. |

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

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

### Semua sesi untuk satu kontak

`GET /chat-sessions/{contactId}`

Mengembalikan setiap sesi obrolan untuk satu kontak. Parameter `status`, `limit`, dan `includeMessages` sama seperti di atas — `hours` tidak berlaku di sini.

**cURL**

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

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

> **Nama bidang ID sesi berbeda di antara kedua endpoint.** Daftar sesi terbaru menyebutnya `session_id` (ini juga memuat detail kontak, karena sesi berasal dari banyak kontak); daftar per-kontak menyebutnya `id`. Nilai mana pun adalah yang Anda berikan sebagai `{sessionId}` saat mengambil utas lengkap di bawah.

Saat `includeMessages=true`, setiap sesi mendapatkan array `messages` yang entri-entrinya memuat `id`, `body`, `direction`, `timestamp`, `type`, `channel`, dan `status`.

---

## Mengambil utas sesi obrolan

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

Sesi obrolan mengelompokkan pesan kontak ke dalam satu jendela percakapan. Titik akhir ini mengembalikan utas lengkap dari satu sesi, **dari yang terlama**, beserta metadata sesi tersebut. Anda dapat menemukan ID sesi untuk kontak melalui titik akhir sesi obrolan.

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

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

Objek `session` melaporkan `status` (`ChatSessionOpened` saat aktif, `ChatSessionClosed` setelah berakhir), `start_date_time`, `end_date_time`, dan `tag` yang dapat dibaca manusia. Larik `messages` menggunakan [bidang pesan](#message-fields) yang sama dengan titik akhir daftar.

---

## Edit, hapus, dan bereaksi terhadap pesan

Endpoint ini mengubah pesan setelah dikirim. Dua di antaranya menjangkau saluran kontak serta salinan Anda sendiri, jadi bacalah pengantar bagian sebelum menghubungkannya — apa yang mungkin dilakukan sepenuhnya bergantung pada saluran tempat percakapan berlangsung.

**Apa yang diizinkan oleh setiap saluran**

| Tindakan | Saluran yang dapat mengubah salinan kontak | Batas waktu |
|---|---|---|
| Edit pesan yang terkirim | Widget obrolan, WhatsApp Web, Telegram, LinkedIn | Tidak ada pada widget obrolan, 15 menit pada WhatsApp Web, 48 jam pada Telegram, 60 menit pada LinkedIn |
| Hapus untuk semua orang | Widget obrolan, WhatsApp Web, Telegram, LinkedIn | 60 menit pada LinkedIn; yang lainnya tidak memiliki batas yang dipublikasikan |
| Bereaksi dengan emoji | WhatsApp Web, Telegram | Tidak ada |

Pada setiap saluran lain — WhatsApp Business API, SMS, Instagram, Messenger, email, LINE, saluran kustom — penghapusan tetap menghapus pesan dari kotak masuk Anda, tetapi kontak tetap menyimpan salinannya, dan pengeditan atau reaksi sama sekali tidak dimungkinkan.

### Edit pesan

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

Menulis ulang pesan yang sudah Anda kirim, baik di perangkat kontak maupun di salinan Anda.

| Bidang | Wajib | Deskripsi |
|---|---|---|
| `body` | Ya | Teks pesan baru. Tidak boleh kosong dan maksimal 4096 karakter. |

Tidak seperti penghapusan, tindakan ini **gagal dengan notifikasi** jika saluran menolak: Anda mendapatkan `409` dan salinan Anda dibiarkan persis seperti yang dimiliki kontak, karena menampilkan editan yang tidak pernah mereka terima akan membuat kedua sisi tidak sinkron. Bidang `edit_reason` memberi tahu Anda alasannya — jendela edit saluran telah ditutup, saluran terputus, atau terjadi kesalahan lain.

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

**Respons** (`200 OK`):

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

Jika saluran tidak menerima editan tersebut, Anda akan mendapatkan `409` sebagai gantinya, dan tidak ada yang diubah:

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

Pesan yang sudah dihapus, saluran yang sama sekali tidak dapat mengedit, dan pesan yang terlalu lama untuk salurannya, semuanya mengembalikan `400` — permintaan tidak pernah mencapai saluran tersebut.

### Hapus satu pesan

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

Menghapus pesan dari percakapan Anda dan, jika saluran mengizinkannya, menarik kembali salinan kontak juga. Tidak ada isi permintaan.

Ini selalu menjawab `200` jika pesan tersebut ada, meskipun salinan kontak tidak dapat ditarik kembali — salinan Anda **sudah** hilang, jadi kesalahan akan menyesatkan. Baca ketiga bidang dalam respons untuk memberi tahu pengguna apa yang sebenarnya terjadi:

| Bidang | Deskripsi |
|---|---|
| `revoke_supported` | Apakah saluran ini dapat menarik kembali pesan atau tidak. |
| `revoked` | Apakah salinan di perangkat kontak telah dihapus. |
| `revoke_reason` | Mengapa pesan tidak dihapus, saat `revoked` adalah `false` — contohnya `revoke_window_closed` atau `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"])
```

**Respons** (`200 OK`):

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

> Pesan yang dihapus tidak akan hilang dari riwayat percakapan. Pesan tersebut tetap ada di `GET /contacts/{contactId}/messages` dengan `is_deleted: true` serta `body` dan `media_url` yang kosong.

### Menghapus beberapa pesan sekaligus

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

Menghapus sekumpulan pesan hanya dari sisi Anda. Isi dan lampiran akan dikosongkan, namun **tidak ada yang ditarik kembali di perangkat kontak** — untuk menarik kembali pesan, hapuslah satu per satu menggunakan endpoint pesan tunggal di atas.

| Bidang | Wajib | Deskripsi |
|---|---|---|
| `message_ids` | Ya | Array ID pesan yang tidak kosong, maksimal 500 per permintaan. `messageIds` diterima sebagai 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"])
```

**Respons** (`200 OK`):

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

### Memberikan reaksi pada pesan

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

Memberikan reaksi emoji Anda sendiri pada sebuah pesan, atau menariknya kembali dengan mengirimkan string kosong. Reaksi milik kontak tidak akan pernah diubah.

| Bidang | Wajib | Deskripsi |
|---|---|---|
| `emoji` | Ya | Emoji untuk memberikan reaksi, atau `""` untuk menghapus reaksi Anda. Harus berupa string tunggal tanpa spasi, maksimal 16 karakter. |

Seperti halnya pengeditan, tindakan ini akan gagal alih-alih menampilkan reaksi yang tidak pernah diterima oleh kontak, dan kegagalan tersebut memberi tahu Anda apakah percobaan ulang layak dilakukan:

- `422` — pesan tidak akan pernah bisa dikirimkan dalam percakapan ini: saluran tidak mendukung reaksi, pesan tidak memiliki ID sisi saluran, atau emoji berada di luar rangkaian yang diizinkan oleh saluran tersebut.
- `409` — saluran tidak dapat dijangkau untuk sementara. Percobaan ulang mungkin berhasil.

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

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

Array `reactions` adalah rangkaian lengkap reaksi yang ada pada pesan saat ini, baik milik Anda maupun milik kontak. Pada `409` atau `422`, array ini dikembalikan tanpa perubahan, sehingga klien yang melakukan rendering langsung darinya tidak akan pernah menampilkan reaksi yang tidak terkirim.

### Memberi peringkat atau menandai pesan

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

Memberi peringkat jempol ke atas atau jempol ke bawah pada pesan dan/atau menandainya sebagai penting. Ini hanyalah pencatatan di sisi Anda saja — tidak ada yang dikirimkan ke kontak.

| Bidang | Wajib | Deskripsi |
|---|---|---|
| `score` | Tidak | `1` jempol ke atas, `-1` jempol ke bawah, `0` menghapus peringkat. |
| `is_important` | Tidak | `true` memberi bintang pada pesan, `false` menghapus bintangnya. Harus berupa boolean asli, bukan string `"true"`. |

Kirim setidaknya salah satu dari keduanya, atau Anda akan mendapatkan `400`. Hanya apa yang Anda kirim yang akan ditulis, jadi memberi bintang pada pesan tidak akan pernah menghapus peringkatnya dan sebaliknya — dan respons hanya akan menggemakan kembali bidang yang Anda kirim.

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

**Respons** (`200 OK`):

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

---

## Menandai pesan sebagai telah dibaca

Anda dapat menghapus status belum dibaca baik untuk pesan tertentu maupun untuk keseluruhan percakapan.

### Menandai pesan tertentu sebagai telah dibaca

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

Teruskan ID pesan yang akan ditandai sebagai telah dibaca.

| Bidang | Wajib | Deskripsi |
|---|---|---|
| `message_ids` | Ya | Larik ID pesan yang tidak kosong (hingga 500 per permintaan). |

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

**Respons** (`200 OK`):

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

### Menandai seluruh obrolan sebagai telah dibaca

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

Menghapus lencana belum dibaca untuk seluruh percakapan kontak di kotak masuk. Tidak diperlukan isi permintaan.

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

**Respons** (`200 OK`):

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

### Tandai seluruh obrolan sebagai belum dibaca

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

Menempatkan kembali lencana belum dibaca pada percakapan — berguna ketika seseorang di tim Anda membuka obrolan tetapi menyerahkannya kembali. Tidak diperlukan isi permintaan.

Ini adalah tanda khusus kotak masuk: ini **tidak** mengubah kapan percakapan terakhir dibaca, jadi tidak ada tanda terima telah dibaca yang dikirim ke kontak pada saluran yang mendukungnya.

**cURL**

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

**Respons** (`200 OK`):

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

---

## Ekspor percakapan

Ekspor memberikan Anda seluruh percakapan sebagai transkrip yang dapat dibaca, alih-alih menelusuri pesan halaman demi halaman. Setiap titik akhir ekspor menerima `filter` berupa `all` (default), `text`, `media`, atau `tool_use`, yang cocok dengan filter pada daftar pesan.

### Ekspor obrolan satu kontak

`GET /chat-exports/{contactId}`

| Parameter kueri | Wajib | Deskripsi |
|---|---|---|
| `format` | Tidak | `txt` (default) mengembalikan tautan unduhan ke transkrip teks biasa. `json` mengembalikan pesan sebagai data terstruktur dalam respons. |
| `filter` | Tidak | `all` (default), `text`, `media`, atau `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"])
```

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

Dengan `format=txt` (default), `data` justru merupakan tautan unduhan ke file transkrip yang dihasilkan:

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

> **Tautan unduhan hanya berlaku singkat.** Ambil file segera setelah Anda mendapatkan tautan alih-alih menyimpannya — minta ekspor baru saat Anda membutuhkan transkrip lagi.

### Ekspor setiap percakapan terbaru

`GET /chat-exports/recent`

Mengekspor percakapan dari semua kontak yang aktif dalam X jam terakhir, dalam satu panggilan.

| Parameter kueri | Wajib | Deskripsi |
|---|---|---|
| `hours` | Ya | Berapa jam aktivitas yang ingin dilihat ke belakang. Harus berupa bilangan bulat positif. |
| `format` | Tidak | `json` (default) mengembalikan satu entri per kontak. `txt` mengembalikan satu file teks yang dapat diunduh berisi setiap percakapan. |
| `limit` | Tidak | Jumlah maksimum kontak yang akan diekspor. Default `50`, maksimum `100`. |
| `filter` | Tidak | `all` (default), `text`, `media` atau `tool_use`. |

**cURL**

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

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

Dengan `format=txt`, responsnya adalah file teks itu sendiri, dikirim sebagai unduhan alih-alih JSON.

> Satu panggilan ini menarik riwayat lengkap dari setiap kontak yang cocok, jadi jaga agar `hours` dan `limit` tetap wajar pada akun yang sibuk.

### Mengirim transkrip melalui email ke kontak

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

Mengirimkan transkrip percakapan mereka sendiri kepada kontak melalui email — alur "kirimkan obrolan ini ke email saya", yang dijalankan dari sistem Anda sendiri.

| Bidang | Wajib | Deskripsi |
|---|---|---|
| `recipient_email` | Tidak | Ke mana harus mengirimnya. Defaultnya adalah alamat email kontak yang tersimpan. |
| `via` | Tidak | `auto` (default) memilih rute terbaik, `transactional` mengirimnya sebagai email sistem, `email_channel` mengirimnya dari saluran email Anda yang terhubung. |
| `note` | Tidak | Baris singkat dari Anda yang ditampilkan di atas transkrip. Hingga 1000 karakter. |

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

**Respons** (`200 OK`):

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

`omittedCount` memberi tahu Anda berapa banyak pesan terlama yang ditinggalkan agar email tetap memiliki panjang yang wajar. `200` berarti transkrip telah dibuat dan dimasukkan ke antrean untuk dikirim, bukan berarti pesan tersebut sudah sampai di kotak masuk.

---

## Jeda atau lanjutkan AI untuk satu kontak

`PUT /contacts/{contactId}`

Atur `is_bot_active` ke `false` untuk menghentikan AI membalas satu kontak, dan kembali ke `true` untuk mengembalikan percakapan. Ini adalah tombol pengambilalihan yang Anda perlukan saat manusia masuk ke dalam percakapan: pesan keluar yang Anda kirim dengan API tetap terkirim saat bot dijeda.

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

**Respons**

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

**Menjeda sebagai bagian dari balasan**

Jika manusia mengambil alih dengan mengirimkan balasan, Anda dapat menjeda bot dalam permintaan yang sama alih-alih melakukan panggilan kedua. `POST /contacts/{contactId}/send-message` menerima dua flag opsional:

| Bidang | Deskripsi |
|---|---|
| `pauseBot` | `true` menjeda AI untuk kontak ini saat pesan dikirim. |
| `clearIncompleteReply` | `true` membuang balasan bot yang setengah jadi agar tidak dilanjutkan setelahnya. |

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

Respons mencakup `"botPaused": true` saat jeda diterapkan.

> Menandai kontak sebagai pribadi dengan [`POST /contacts/bulk-flag`](contacts.md) juga menjeda bot untuk mereka. Lihat [Kontak](contacts.md) untuk daftar bidang lengkap.

---

## Membangun kotak masuk Anda sendiri

Segala sesuatu yang dibutuhkan kotak masuk ada di halaman ini dan di [Kontak](contacts.md):

| Apa yang Anda butuhkan | Endpoint |
|---|---|
| Mencantumkan percakapan | `GET /contacts` |
| Membaca percakapan | `GET /contacts/{contactId}/messages` |
| Mencantumkan sesi obrolan kontak | `GET /chat-sessions/{contactId}` |
| Melihat apa yang baru saja masuk | `GET /chat-sessions/recent` |
| Membaca satu sesi obrolan | `GET /contacts/{contactId}/chat-sessions/{sessionId}/messages` |
| Mengirim balasan manual | `POST /contacts/{contactId}/send-message` |
| Mengoreksi balasan yang baru saja Anda kirim | `POST /contacts/{contactId}/messages/{messageId}/edit` |
| Menghapus pesan | `DELETE /contacts/{contactId}/messages/{messageId}` |
| Menghapus beberapa pesan | `POST /contacts/{contactId}/messages/bulk-delete` |
| Bereaksi dengan emoji | `POST /contacts/{contactId}/messages/{messageId}/react` |
| Memberi peringkat atau membintangi pesan | `PATCH /contacts/{contactId}/messages/{messageId}` |
| Menandai sebagai telah dibaca | `POST /contacts/{contactId}/mark-read` |
| Mengembalikan obrolan ke tim | `POST /contacts/{contactId}/mark-unread` |
| Mengekspor transkrip | `GET /chat-exports/{contactId}` |
| Menjeda atau melanjutkan AI | `PUT /contacts/{contactId}` dengan `is_bot_active` |

Untuk pembaruan langsung, berlanggananlah ke acara `New Message`, `Replies`, `Human Alerted`, dan `Chat Concluded` dengan [Webhook](webhooks.md) alih-alih melakukan polling pada API ini secara berkala.

---

## Kesalahan API Pesan

Endpoint pesan mengembalikan amplop kesalahan standar:

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

| Status | Kapan ini terjadi pada endpoint pesan |
|---|---|
| `400` | Bidang wajib hilang atau parameter tidak valid (bad `limit`, `hours`, `filter`, `direction`, `status`, array `message_ids` kosong atau lebih dari 500, `cursor` tidak valid, edit `body` kosong atau terlalu panjang, `score` di luar `-1`/`0`/`1`, atau emoji dengan spasi atau lebih dari 16 karakter). Juga dikembalikan saat pesan tidak dapat diedit sama sekali — pesan telah dihapus, salurannya tidak memiliki fitur edit, atau sudah melewati jendela edit saluran tersebut. |
| `404` | Kontak, sesi obrolan, atau salah satu ID pesan yang diberikan tidak ditemukan. |
| `409` | Saluran tidak dapat menerima perubahan saat ini. Tidak ada yang ditulis: saat pengeditan, `edit_reason` menjelaskan alasannya; saat reaksi, saluran tidak dapat dijangkau untuk sementara dan mencoba lagi mungkin berhasil. |
| `422` | Kontak tidak dapat menerima pesan keluar (jangan ganggu, pribadi, atau saluran yang tidak didukung), atau reaksi tidak pernah dapat dikirimkan pada percakapan ini (`reaction_reason` menjelaskan yang mana). |

Kode bersama yang dapat dikembalikan oleh setiap endpoint — `401`, `403` (paket Anda tidak menyertakan akses API), `429` (batas kecepatan) dan `500` — tercantum beserta panduan percobaan ulang di [Kesalahan & Penomoran Halaman](errors-and-pagination.md).

---

## Langkah berikutnya

- [Webhook](webhooks.md) — dapatkan pembaruan status pengiriman yang dikirimkan kepada Anda alih-alih melakukan polling.
- [Kontak](contacts.md) — buat dan cari kontak yang Anda kirimi pesan.
- [Janji Temu](appointments.md) — pesan dan kelola janji temu untuk kontak Anda.
