
# API Câu hỏi thường gặp (FAQs)

FAQs là các mục hỏi đáp mà bot AI của bạn sử dụng khi trả lời khách hàng. Mỗi FAQ thuộc về tài khoản của bạn và có thể được liên kết với một hoặc nhiều chiến dịch, nhờ đó cùng một câu trả lời có thể được tái sử dụng ở bất cứ nơi nào phù hợp. API FAQs cho phép bạn quản lý thư viện đó theo lập trình — tạo, cập nhật, nhập hàng loạt, sắp xếp lại và liên kết các FAQ với các chiến dịch từ mã nguồn của riêng bạn.

Tất cả các endpoint bên dưới đều tương đối so với URL cơ sở `https://api.youraiconnector.com/v1`. Mọi yêu cầu đều phải được xác thực — xem [Truy cập API](../integrations/api-access.md) và [Xác thực](authentication.md). Truy cập API là một tính năng trả phí; nếu không có quyền truy cập, các yêu cầu sẽ bị từ chối với mã `403`.

> **Cách bot sử dụng FAQ:** Khi bạn tạo hoặc thay đổi một FAQ, nền tảng sẽ chuẩn bị dữ liệu tìm kiếm của nó (được sử dụng để khớp FAQ với các câu hỏi đến) trong nền. Quá trình này thường hoàn tất trong vài giây, sau đó bot sẽ tự động bắt đầu sử dụng mục này.


---

## Đối tượng FAQ

Mỗi FAQ trả về từ API có cấu trúc như sau:

| Trường | Kiểu | Mô tả |
|---|---|---|
| `id` | string | Mã định danh duy nhất của FAQ. |
| `question` | string | Câu hỏi của khách hàng mà mục này giải đáp. |
| `answer` | string | Câu trả lời mà bot AI đưa ra. |
| `category` | string \| null | Nhãn danh mục tùy chọn ở dạng tự do. |
| `tags` | string[] | Các nhãn tùy chọn để sắp xếp các FAQ. |
| `is_active` | boolean | Liệu bot có được phép sử dụng FAQ này hay không. Mặc định là `true`. |
| `is_global` | boolean | Đánh dấu FAQ là không gắn liền với một chiến dịch hoặc Tác nhân cụ thể nào. Điều này không làm cho FAQ áp dụng ở mọi nơi: một FAQ chỉ được sử dụng bởi các chiến dịch và Tác nhân mà nó được liên kết. Mặc định là `false`. |
| `usage_count` | integer | Số lần FAQ này đã được sử dụng trong các phản hồi của AI. |
| `order_index` | integer | Vị trí hiển thị của FAQ này trong chiến dịch của nó. |
| `campaign_ids` | string[] | ID của các chiến dịch mà FAQ này được liên kết. |
| `created_at` | string \| null | Dấu thời gian ISO 8601 về thời điểm FAQ được tạo. |
| `updated_at` | string \| null | Dấu thời gian ISO 8601 về lần thay đổi cuối cùng. |

Các trường bạn có thể **thiết lập** là: `question`, `answer`, `is_active`, `is_global`, `category`, `tags`, và `order_index`. Nền tảng quản lý mọi thứ khác (dữ liệu tìm kiếm, số lần sử dụng, dấu thời gian); bất kỳ trường nào khác trong phần thân yêu cầu của bạn đều sẽ bị bỏ qua.

---

## Liệt kê các FAQ

`GET /faqs`

Trả về các FAQ trong tài khoản của bạn, hiển thị mục mới nhất trước. Tùy chọn lọc theo một chiến dịch duy nhất hoặc theo trạng thái hoạt động.

**Các tham số truy vấn**

| Tham số | Bắt buộc | Mô tả |
|---|---|---|
| `campaign_id` | Không | Chỉ trả về các FAQ được liên kết với chiến dịch này. |
| `is_active` | Không | Chỉ trả về các FAQ có trạng thái hoạt động này (`true` hoặc `false`). Bộ lọc này được áp dụng trên mỗi trang, vì vậy một trang có thể chứa ít mục hơn `limit`. |
| `limit` | Không | Số lượng FAQ tối đa trên mỗi trang. Mặc định là `50`, tối đa là `100`. |
| `cursor` | Không | ID của FAQ để tiếp tục sau đó. Truyền giá trị `next_cursor` từ trang trước. |

**cURL**

```bash
curl "https://api.youraiconnector.com/v1/faqs?campaign_id=campaign123&limit=50&apiKey=YOUR_API_KEY"
```

**JavaScript**

```javascript
const res = await fetch(
  "https://api.youraiconnector.com/v1/faqs?campaign_id=campaign123&limit=50",
  { headers: { "X-API-Key": "YOUR_API_KEY" } }
);
const data = await res.json();
console.log(data.faqs, data.next_cursor);
```

**Python**

```python
import requests

res = requests.get(
    "https://api.youraiconnector.com/v1/faqs",
    params={"campaign_id": "campaign123", "limit": 50},
    headers={"X-API-Key": "YOUR_API_KEY"},
)
data = res.json()
print(data["faqs"], data["next_cursor"])
```

**Phản hồi**

```json
{
  "success": true,
  "faqs": [
    {
      "id": "aBcD1234eFgH5678",
      "question": "How long does shipping take?",
      "answer": "Standard shipping takes 3-5 business days.",
      "category": "shipping",
      "tags": ["logistics", "delivery"],
      "is_active": true,
      "is_global": false,
      "usage_count": 12,
      "order_index": 0,
      "campaign_ids": ["campaign123"],
      "created_at": "2026-01-01T12:00:00.000Z",
      "updated_at": "2026-01-02T08:30:00.000Z"
    }
  ],
  "next_cursor": "aBcD1234eFgH5678"
}
```

Khi `next_cursor` là `null`, sẽ không còn kết quả nào nữa.

---

## Lấy một FAQ

`GET /faqs/{faqId}`

Trả về một FAQ duy nhất theo ID của nó.

**cURL**

```bash
curl "https://api.youraiconnector.com/v1/faqs/aBcD1234eFgH5678?apiKey=YOUR_API_KEY"
```

**JavaScript**

```javascript
const res = await fetch(
  "https://api.youraiconnector.com/v1/faqs/aBcD1234eFgH5678",
  { headers: { "X-API-Key": "YOUR_API_KEY" } }
);
const { faq } = await res.json();
```

**Python**

```python
import requests

res = requests.get(
    "https://api.youraiconnector.com/v1/faqs/aBcD1234eFgH5678",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
faq = res.json()["faq"]
```

**Phản hồi**

```json
{
  "success": true,
  "faq": {
    "id": "aBcD1234eFgH5678",
    "question": "How long does shipping take?",
    "answer": "Standard shipping takes 3-5 business days.",
    "category": "shipping",
    "tags": ["logistics"],
    "is_active": true,
    "is_global": false,
    "usage_count": 12,
    "order_index": 0,
    "campaign_ids": ["campaign123"],
    "created_at": "2026-01-01T12:00:00.000Z",
    "updated_at": "2026-01-02T08:30:00.000Z"
  }
}
```

---

## Tạo một FAQ

`POST /faqs`

Tạo một FAQ mới và liên kết nó với một chiến dịch.

**Các trường yêu cầu**

| Trường | Bắt buộc | Mô tả |
|---|---|---|
| `campaign_id` | Có | Chiến dịch để liên kết FAQ mới vào. |
| `question` | Có | Câu hỏi của khách hàng mà mục này trả lời. |
| `answer` | Có | Câu trả lời mà bot nên đưa ra. |
| `is_active` | Không | Liệu bot có được phép sử dụng FAQ này hay không. Mặc định là `true`. |
| `is_global` | Không | Liệu FAQ này có áp dụng cho tất cả các chiến dịch hay không. Mặc định là `false`. |
| `category` | Không | Một nhãn danh mục tự do. |
| `tags` | Không | Một mảng các nhãn. |
| `order_index` | Không | Vị trí hiển thị trong chiến dịch. Mặc định là `0`. |

**cURL**

```bash
curl -X POST "https://api.youraiconnector.com/v1/faqs?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "campaign_id": "campaign123",
    "question": "How long does shipping take?",
    "answer": "Standard shipping takes 3-5 business days.",
    "category": "shipping",
    "tags": ["logistics"]
  }'
```

**JavaScript**

```javascript
const res = await fetch("https://api.youraiconnector.com/v1/faqs", {
  method: "POST",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    campaign_id: "campaign123",
    question: "How long does shipping take?",
    answer: "Standard shipping takes 3-5 business days.",
    category: "shipping",
    tags: ["logistics"],
  }),
});
const { faq_id } = await res.json();
```

**Python**

```python
import requests

res = requests.post(
    "https://api.youraiconnector.com/v1/faqs",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "campaign_id": "campaign123",
        "question": "How long does shipping take?",
        "answer": "Standard shipping takes 3-5 business days.",
        "category": "shipping",
        "tags": ["logistics"],
    },
)
faq_id = res.json()["faq_id"]
```

**Phản hồi**

```json
{
  "success": true,
  "faq_id": "aBcD1234eFgH5678"
}
```

---

## Cập nhật FAQ

`PUT /faqs/{faqId}`

Cập nhật một phần FAQ. Chỉ các trường có thể ghi được cung cấp mới bị thay đổi; mọi thứ khác vẫn giữ nguyên giá trị hiện tại. Việc thay đổi `question` hoặc `answer` sẽ tự động làm mới dữ liệu tìm kiếm của FAQ trong nền.

Nếu bạn gửi `question` hoặc `answer`, chúng phải là các chuỗi không trống. Việc không gửi các trường có thể ghi được nhận dạng nào sẽ trả về `400`.

**cURL**

```bash
curl -X PUT "https://api.youraiconnector.com/v1/faqs/aBcD1234eFgH5678?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "is_active": false }'
```

**JavaScript**

```javascript
const res = await fetch(
  "https://api.youraiconnector.com/v1/faqs/aBcD1234eFgH5678",
  {
    method: "PUT",
    headers: {
      "X-API-Key": "YOUR_API_KEY",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ is_active: false }),
  }
);
const data = await res.json();
```

**Python**

```python
import requests

res = requests.put(
    "https://api.youraiconnector.com/v1/faqs/aBcD1234eFgH5678",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={"is_active": False},
)
data = res.json()
```

**Phản hồi**

```json
{
  "success": true,
  "faq_id": "aBcD1234eFgH5678"
}
```

---

## Xóa FAQ

`DELETE /faqs/{faqId}`

Xóa vĩnh viễn một FAQ. Tùy chọn truyền `campaign_id` dưới dạng tham số truy vấn để cũng xóa FAQ đó khỏi danh sách FAQ của chiến dịch đó.

**Các tham số truy vấn**

| Tham số | Bắt buộc | Mô tả |
|---|---|---|
| `campaign_id` | Không | Đồng thời xóa FAQ khỏi danh sách FAQ của chiến dịch này. |

**cURL**

```bash
curl -X DELETE "https://api.youraiconnector.com/v1/faqs/aBcD1234eFgH5678?campaign_id=campaign123&apiKey=YOUR_API_KEY"
```

**JavaScript**

```javascript
const res = await fetch(
  "https://api.youraiconnector.com/v1/faqs/aBcD1234eFgH5678?campaign_id=campaign123",
  { method: "DELETE", headers: { "X-API-Key": "YOUR_API_KEY" } }
);
const data = await res.json();
```

**Python**

```python
import requests

res = requests.delete(
    "https://api.youraiconnector.com/v1/faqs/aBcD1234eFgH5678",
    params={"campaign_id": "campaign123"},
    headers={"X-API-Key": "YOUR_API_KEY"},
)
data = res.json()
```

**Phản hồi**

```json
{
  "success": true
}
```

---

## Xóa hàng loạt FAQ

`POST /faqs/bulk-delete`

Xóa tối đa 500 FAQ trong một yêu cầu duy nhất. Khi `campaign_id` được cung cấp, các FAQ đã xóa cũng sẽ bị loại bỏ khỏi danh sách FAQ của chiến dịch đó.

**Các trường yêu cầu**

| Trường | Bắt buộc | Mô tả |
|---|---|---|
| `faq_ids` | Có | Một mảng không trống chứa các ID FAQ cần xóa (tối đa 500). |
| `campaign_id` | Không | Đồng thời xóa các FAQ đã xóa khỏi danh sách FAQ của chiến dịch này. |

**cURL**

```bash
curl -X POST "https://api.youraiconnector.com/v1/faqs/bulk-delete?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "faq_ids": ["faqId1", "faqId2"], "campaign_id": "campaign123" }'
```

**JavaScript**

```javascript
const res = await fetch("https://api.youraiconnector.com/v1/faqs/bulk-delete", {
  method: "POST",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    faq_ids: ["faqId1", "faqId2"],
    campaign_id: "campaign123",
  }),
});
const data = await res.json();
```

**Python**

```python
import requests

res = requests.post(
    "https://api.youraiconnector.com/v1/faqs/bulk-delete",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={"faq_ids": ["faqId1", "faqId2"], "campaign_id": "campaign123"},
)
data = res.json()
```

**Phản hồi**

```json
{
  "success": true,
  "deleted_count": 2
}
```

---

## Câu hỏi thường gặp về Nhập dữ liệu

`POST /faqs/import`

Nhập hàng loạt tối đa 500 câu hỏi thường gặp (FAQ) và liên kết tất cả chúng với một chiến dịch. Các mục có `question` khớp với FAQ hiện có trong thư viện của bạn (không phân biệt chữ hoa chữ thường) sẽ **cập nhật** FAQ đó thay vì tạo bản sao trùng lặp.

> **Mẹo hiệu suất:** Việc khớp dữ liệu trùng lặp sẽ quét toàn bộ thư viện FAQ của bạn, vì vậy các thư viện rất lớn sẽ làm chậm quá trình nhập. Hãy ưu tiên nhập ít lần với số lượng lớn thay vì nhập nhiều lần với số lượng nhỏ.

**Các trường yêu cầu**

| Trường | Bắt buộc | Mô tả |
|---|---|---|
| `campaign_id` | Có | Chiến dịch mà tất cả các FAQ được nhập vào sẽ liên kết tới. |
| `faqs` | Có | Một mảng không trống chứa các mục FAQ (tối đa 500). Mỗi mục phải có `question` và `answer` không trống; nó cũng có thể bao gồm `is_active`, `is_global`, `category`, `tags` và `order_index`. |

**cURL**

```bash
curl -X POST "https://api.youraiconnector.com/v1/faqs/import?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "campaign_id": "campaign123",
    "faqs": [
      { "question": "Do you ship internationally?", "answer": "Yes, we ship to most countries worldwide." },
      { "question": "What is your return policy?", "answer": "You can return any item within 30 days." }
    ]
  }'
```

**JavaScript**

```javascript
const res = await fetch("https://api.youraiconnector.com/v1/faqs/import", {
  method: "POST",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    campaign_id: "campaign123",
    faqs: [
      {
        question: "Do you ship internationally?",
        answer: "Yes, we ship to most countries worldwide.",
      },
      {
        question: "What is your return policy?",
        answer: "You can return any item within 30 days.",
      },
    ],
  }),
});
const data = await res.json();
```

**Python**

```python
import requests

res = requests.post(
    "https://api.youraiconnector.com/v1/faqs/import",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "campaign_id": "campaign123",
        "faqs": [
            {"question": "Do you ship internationally?", "answer": "Yes, we ship to most countries worldwide."},
            {"question": "What is your return policy?", "answer": "You can return any item within 30 days."},
        ],
    },
)
data = res.json()
```

**Phản hồi**

```json
{
  "success": true,
  "faq_ids": ["aBcD1234eFgH5678", "iJkL9012mNoP3456"],
  "imported_count": 2
}
```

`faq_ids` là các ID FAQ đã được tạo hoặc cập nhật, theo thứ tự bạn đã cung cấp.

---

## Sắp xếp lại FAQ

`POST /faqs/reorder`

Thiết lập thứ tự hiển thị các FAQ của một chiến dịch. Hãy cung cấp danh sách **đầy đủ** các ID FAQ theo thứ tự mong muốn; vị trí của mỗi FAQ sẽ được cập nhật để khớp với vị trí của nó trong mảng.

**Các trường yêu cầu**

| Trường | Bắt buộc | Mô tả |
|---|---|---|
| `campaign_id` | Có | Chiến dịch có các câu hỏi thường gặp (FAQ) đang được sắp xếp lại. |
| `ordered_faq_ids` | Có | Một mảng không trống chứa tất cả các ID FAQ của chiến dịch theo thứ tự hiển thị mong muốn (tối đa 500). |

**cURL**

```bash
curl -X POST "https://api.youraiconnector.com/v1/faqs/reorder?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "campaign_id": "campaign123",
    "ordered_faq_ids": ["faqId2", "faqId1", "faqId3"]
  }'
```

**JavaScript**

```javascript
const res = await fetch("https://api.youraiconnector.com/v1/faqs/reorder", {
  method: "POST",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    campaign_id: "campaign123",
    ordered_faq_ids: ["faqId2", "faqId1", "faqId3"],
  }),
});
const data = await res.json();
```

**Python**

```python
import requests

res = requests.post(
    "https://api.youraiconnector.com/v1/faqs/reorder",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "campaign_id": "campaign123",
        "ordered_faq_ids": ["faqId2", "faqId1", "faqId3"],
    },
)
data = res.json()
```

**Phản hồi**

```json
{
  "success": true
}
```

Nếu chiến dịch hoặc bất kỳ ID FAQ nào không được tìm thấy trong tài khoản của bạn, yêu cầu sẽ trả về `404 One or more FAQs were not found`.

---

## Liên kết FAQ với một chiến dịch

`POST /faqs/{faqId}/link`

Liên kết một FAQ hiện có với một chiến dịch bổ sung. Một FAQ có thể được chia sẻ bởi bất kỳ số lượng chiến dịch nào, vì vậy cùng một câu trả lời chỉ cần được duy trì một lần.

**Các trường yêu cầu**

| Trường | Bắt buộc | Mô tả |
|---|---|---|
| `campaign_id` | Có | Chiến dịch để liên kết FAQ với. |

**cURL**

```bash
curl -X POST "https://api.youraiconnector.com/v1/faqs/aBcD1234eFgH5678/link?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "campaign_id": "campaign456" }'
```

**JavaScript**

```javascript
const res = await fetch(
  "https://api.youraiconnector.com/v1/faqs/aBcD1234eFgH5678/link",
  {
    method: "POST",
    headers: {
      "X-API-Key": "YOUR_API_KEY",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ campaign_id: "campaign456" }),
  }
);
const data = await res.json();
```

**Python**

```python
import requests

res = requests.post(
    "https://api.youraiconnector.com/v1/faqs/aBcD1234eFgH5678/link",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={"campaign_id": "campaign456"},
)
data = res.json()
```

**Phản hồi**

```json
{
  "success": true,
  "faq_id": "aBcD1234eFgH5678",
  "campaign_id": "campaign456"
}
```

---

## Hủy liên kết FAQ khỏi chiến dịch

`POST /faqs/{faqId}/unlink`

Xóa FAQ khỏi một chiến dịch mà không xóa chính FAQ đó. FAQ vẫn nằm trong thư viện của bạn và vẫn được liên kết với các chiến dịch khác.

**Các trường yêu cầu**

| Trường | Bắt buộc | Mô tả |
|---|---|---|
| `campaign_id` | Có | Chiến dịch cần xóa FAQ khỏi đó. |

**cURL**

```bash
curl -X POST "https://api.youraiconnector.com/v1/faqs/aBcD1234eFgH5678/unlink?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "campaign_id": "campaign456" }'
```

**JavaScript**

```javascript
const res = await fetch(
  "https://api.youraiconnector.com/v1/faqs/aBcD1234eFgH5678/unlink",
  {
    method: "POST",
    headers: {
      "X-API-Key": "YOUR_API_KEY",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ campaign_id: "campaign456" }),
  }
);
const data = await res.json();
```

**Python**

```python
import requests

res = requests.post(
    "https://api.youraiconnector.com/v1/faqs/aBcD1234eFgH5678/unlink",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={"campaign_id": "campaign456"},
)
data = res.json()
```

**Phản hồi**

```json
{
  "success": true,
  "faq_id": "aBcD1234eFgH5678",
  "campaign_id": "campaign456"
}
```

---

## Xây dựng lại dữ liệu tìm kiếm của FAQ

`POST /faqs/{faqId}/rebuild-embeddings`

Xếp hàng chờ xây dựng lại dữ liệu mà bot AI sử dụng để tìm FAQ này (dữ liệu tìm kiếm theo ngữ nghĩa và từ khóa). Việc này hữu ích nếu FAQ không được chọn trong các câu trả lời như mong đợi. Quá trình xây dựng lại chạy trong nền và thường hoàn tất trong vài giây; FAQ có thể tạm thời bị loại khỏi các câu trả lời của AI trong khi đang được xây dựng lại.

Điểm cuối này trả về `202 Accepted` vì công việc vẫn tiếp tục sau khi phản hồi được gửi đi. `status` luôn là `"processing"` — hãy tìm nạp lại FAQ sau nếu bạn cần xác nhận việc hoàn tất.

**cURL**

```bash
curl -X POST "https://api.youraiconnector.com/v1/faqs/aBcD1234eFgH5678/rebuild-embeddings?apiKey=YOUR_API_KEY"
```

**JavaScript**

```javascript
const res = await fetch(
  "https://api.youraiconnector.com/v1/faqs/aBcD1234eFgH5678/rebuild-embeddings",
  { method: "POST", headers: { "X-API-Key": "YOUR_API_KEY" } }
);
const data = await res.json();
```

**Python**

```python
import requests

res = requests.post(
    "https://api.youraiconnector.com/v1/faqs/aBcD1234eFgH5678/rebuild-embeddings",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
data = res.json()
```

**Phản hồi**

```json
{
  "success": true,
  "faq_id": "aBcD1234eFgH5678",
  "status": "processing"
}
```

---

## Quản lý FAQ có hỗ trợ AI

Các điểm cuối dưới đây vượt xa các thao tác CRUD thông thường: chúng gọi cùng các công cụ hỗ trợ AI mà trình chỉnh sửa FAQ trên bảng điều khiển sử dụng — tìm các mục trùng lặp, tạo các mục từ tài liệu và khớp FAQ với các tác vụ thiếu hụt kiến thức mở. Phần thân yêu cầu (request bodies) trong tập hợp này sử dụng các tên trường `camelCase` (`campaignId`, `taskId`, `sourceIds`...), khớp với các hình dạng yêu cầu của chính ứng dụng, thay vì `snake_case` được sử dụng ở nơi khác trên trang này — hãy sao chép các ví dụ bên dưới thay vì đoán tên trường.

### Tạo bản sao FAQ dành riêng cho chiến dịch

`POST /faqs/{faqId}/fork-for-campaign`

Tạo một FAQ mới là bản sao của một FAQ hiện có, được giới hạn trong một chiến dịch duy nhất và liên kết lại chiến dịch đó với bản sao mới thay vì bản gốc. Sử dụng tính năng này khi bạn muốn tùy chỉnh câu trả lời cho một chiến dịch mà không làm thay đổi nó ở mọi nơi khác mà FAQ gốc được sử dụng. FAQ gốc vẫn được giữ nguyên — nó chỉ mất liên kết với chiến dịch này.

**Các trường yêu cầu**

| Trường | Bắt buộc | Mô tả |
|---|---|---|
| `campaign_id` | Có | Chiến dịch để giới hạn phạm vi cho bản sao mới và để liên kết lại từ FAQ gốc. |
| `question` | Có | Câu hỏi cho bản sao mới, dành riêng cho chiến dịch. |
| `answer` | Có | Câu trả lời cho bản sao mới, dành riêng cho chiến dịch. |

**cURL**

```bash
curl -X POST "https://api.youraiconnector.com/v1/faqs/aBcD1234eFgH5678/fork-for-campaign?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "campaign_id": "campaign456",
    "question": "How long does shipping take to the EU?",
    "answer": "For EU orders, shipping takes 7-10 business days."
  }'
```

**JavaScript**

```javascript
const res = await fetch(
  "https://api.youraiconnector.com/v1/faqs/aBcD1234eFgH5678/fork-for-campaign",
  {
    method: "POST",
    headers: {
      "X-API-Key": "YOUR_API_KEY",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      campaign_id: "campaign456",
      question: "How long does shipping take to the EU?",
      answer: "For EU orders, shipping takes 7-10 business days.",
    }),
  }
);
const data = await res.json();
```

**Python**

```python
import requests

res = requests.post(
    "https://api.youraiconnector.com/v1/faqs/aBcD1234eFgH5678/fork-for-campaign",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "campaign_id": "campaign456",
        "question": "How long does shipping take to the EU?",
        "answer": "For EU orders, shipping takes 7-10 business days.",
    },
)
data = res.json()
```

**Phản hồi** — `201 Created`

```json
{
  "success": true,
  "faq_id": "nEwFaQiD9012mNoP",
  "campaign_id": "campaign456",
  "original_faq_id": "aBcD1234eFgH5678"
}
```

### Tìm các FAQ gần như trùng lặp

`POST /faqs/dedupe`

Bắt đầu một tác vụ chạy ngầm để quét thư viện FAQ của bạn nhằm tìm các mục gần như trùng lặp hoặc chồng chéo và hợp nhất hoặc xóa chúng khi hệ thống tự tin. Hữu ích sau khi nhập dữ liệu hàng loạt hoặc sau vài vòng tạo FAQ bằng AI khiến thư viện bị chồng chéo. Mỗi tài khoản chỉ có thể chạy một tác vụ khử trùng lặp tại một thời điểm — bắt đầu tác vụ thứ hai khi tác vụ đầu tiên vẫn đang chạy sẽ trả về `409`.

**Các trường yêu cầu**

| Trường | Bắt buộc | Mô tả |
|---|---|---|
| `sourceIds` | Không | Mảng các ID nguồn cơ sở kiến thức để giới hạn phạm vi khử trùng lặp. Bỏ qua để quét toàn bộ thư viện FAQ của bạn. |

**cURL**

```bash
curl -X POST "https://api.youraiconnector.com/v1/faqs/dedupe?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'
```

**JavaScript**

```javascript
const res = await fetch("https://api.youraiconnector.com/v1/faqs/dedupe", {
  method: "POST",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({}),
});
const data = await res.json();
```

**Python**

```python
import requests

res = requests.post(
    "https://api.youraiconnector.com/v1/faqs/dedupe",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={},
)
data = res.json()
```

**Phản hồi** — `202 Accepted`

```json
{
  "success": true,
  "job_id": "dedupJob_aBc123"
}
```

Tác vụ chạy trong nền và thường mất vài phút đối với thư viện lớn. Không có điểm cuối trạng thái riêng biệt — hãy tìm nạp lại [`GET /faqs`](#list-faqs) sau một thời gian ngắn chờ đợi để xem những gì đã thay đổi. Khi bạn xem xong kết quả, hãy gọi điểm cuối loại bỏ (dismiss) bên dưới để xóa nó.

### Loại bỏ kết quả kiểm tra trùng lặp

`POST /faqs/dedupe/dismiss`

Xóa công việc chống trùng lặp đã hoàn tất để nó không còn hiển thị dưới dạng kết quả đang hoạt động. Idempotent (có tính lũy đẳng) — an toàn để gọi ngay cả khi không có gì cần loại bỏ. Trả về `409` nếu công việc vẫn đang `queued` hoặc `processing` (bạn không thể loại bỏ một lần chạy chưa hoàn tất).

**cURL**

```bash
curl -X POST "https://api.youraiconnector.com/v1/faqs/dedupe/dismiss?apiKey=YOUR_API_KEY"
```

**JavaScript**

```javascript
const res = await fetch("https://api.youraiconnector.com/v1/faqs/dedupe/dismiss", {
  method: "POST",
  headers: { "X-API-Key": "YOUR_API_KEY" },
});
const data = await res.json();
```

**Python**

```python
import requests

res = requests.post(
    "https://api.youraiconnector.com/v1/faqs/dedupe/dismiss",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
data = res.json()
```

**Phản hồi**

```json
{ "success": true }
```

### Tạo câu hỏi thường gặp (FAQ) từ các tài liệu đã tải lên

`POST /faqs/generate-from-documents`

Đọc một hoặc nhiều tài liệu đã có trong kho lưu trữ tệp của tài khoản của bạn và yêu cầu AI soạn thảo các câu hỏi thường gặp từ nội dung của chúng, kiểm tra các bản nháp so với thư viện hiện có của bạn để tái sử dụng hoặc cập nhật các mục thay vì tạo ra các bản sao trùng lặp. Kết quả **không** được ghi lại ngay lập tức — chúng được lưu trữ dưới dạng tập hợp thay đổi đang chờ xử lý trên chiến dịch để bạn xem xét, sau đó được áp dụng (hoặc loại bỏ) bằng [Áp dụng các thay đổi FAQ đã xem xét](#apply-reviewed-faq-changes) bên dưới. Việc này tốn tín dụng vì đây là lượt tạo nội dung bằng AI dựa trên văn bản tài liệu.

Điểm cuối này không chứa tệp: `storagePath` phải trỏ đến một tệp đã nằm trong thư mục tải lên của riêng bạn (`users/{your user id}/uploads/`), theo cùng quy ước như [Nhập tài liệu đã tải lên](knowledge-base.md#import-an-uploaded-document) trên API Cơ sở Kiến thức.

**Các trường yêu cầu**

| Trường | Bắt buộc | Mô tả |
|---|---|---|
| `campaignId` | Có | Chiến dịch mà các FAQ được tạo ra đề xuất cho. |
| `uploadedFiles` | Có | Mảng tệp không trống để đọc, mỗi tệp là `{ storagePath, fileName, mimeType }`. `storagePath` phải bắt đầu bằng `users/{your user id}/uploads/`. |

**cURL**

```bash
curl -X POST "https://api.youraiconnector.com/v1/faqs/generate-from-documents?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "campaignId": "campaign123",
    "uploadedFiles": [
      { "storagePath": "users/abc123uid/uploads/handbook.pdf", "fileName": "handbook.pdf", "mimeType": "application/pdf" }
    ]
  }'
```

**JavaScript**

```javascript
const res = await fetch("https://api.youraiconnector.com/v1/faqs/generate-from-documents", {
  method: "POST",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    campaignId: "campaign123",
    uploadedFiles: [
      { storagePath: "users/abc123uid/uploads/handbook.pdf", fileName: "handbook.pdf", mimeType: "application/pdf" },
    ],
  }),
});
const data = await res.json();
```

**Python**

```python
import requests

res = requests.post(
    "https://api.youraiconnector.com/v1/faqs/generate-from-documents",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "campaignId": "campaign123",
        "uploadedFiles": [
            {"storagePath": "users/abc123uid/uploads/handbook.pdf", "fileName": "handbook.pdf", "mimeType": "application/pdf"},
        ],
    },
)
data = res.json()
```

**Phản hồi** — `202 Accepted`

```json
{
  "success": true,
  "faqCount": 6,
  "reusedCount": 2,
  "modifiedCount": 1,
  "newCount": 3
}
```

`faqCount` là tổng số thay đổi được đề xuất đang chờ xem xét; `reusedCount`, `modifiedCount` và `newCount` phân tách số đó thành các FAQ khớp với một mục hiện có mà không thay đổi, các mục mà AI đề xuất chỉnh sửa và các mục hoàn toàn mới. Các tệp đã tải lên sẽ bị xóa khỏi bộ lưu trữ sau khi quá trình xử lý kết thúc, bất kể thành công hay không.

### Áp dụng các thay đổi FAQ đã xem xét

`POST /faqs/apply-optimization`

Áp dụng (hoặc loại bỏ) một tập hợp các thay đổi FAQ do AI đề xuất đang chờ xử lý — loại thay đổi được tạo bởi [Tạo FAQ từ tài liệu](#generate-faqs-from-uploaded-documents) ở trên, hoặc bởi đánh giá tối ưu hóa FAQ của bảng điều khiển. Bạn chọn chính xác những thay đổi đề xuất nào để chấp nhận; bất kỳ thay đổi nào bạn không đề cập đến sẽ được giữ nguyên (một thay đổi bị bỏ qua không bao giờ được coi là sự từ chối dẫn đến việc xóa nội dung).

**Các trường yêu cầu**

| Trường | Bắt buộc | Mô tả |
|---|---|---|
| `campaignId` | Một trong hai | Chiến dịch có các thay đổi FAQ đang chờ xử lý được áp dụng. |
| `agentId` | Một trong hai | AI Agent có các thay đổi FAQ đang chờ xử lý được áp dụng, trên tài khoản gốc của tác nhân. Cung cấp chính xác một trong `campaignId` / `agentId`, không bao giờ cung cấp cả hai. |
| `acceptedChanges` | Có | Mảng các thay đổi bạn chấp nhận, mỗi thay đổi là `{ action, faq_id?, faq_ref_path?, question?, answer?, edit_scope? }`. `action` là một trong `keep`, `remove`, `add_from_library`, `create_new`, `modify`. Gửi một mảng trống để loại bỏ tập hợp đang chờ xử lý mà không áp dụng bất kỳ thay đổi nào. |

**cURL**

```bash
curl -X POST "https://api.youraiconnector.com/v1/faqs/apply-optimization?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "campaignId": "campaign123",
    "acceptedChanges": [
      { "action": "create_new", "question": "Do you ship to the EU?", "answer": "Yes, EU shipping takes 7-10 business days." },
      { "action": "remove", "faq_ref_path": "users/abc123uid/faqs/oldFaqId" }
    ]
  }'
```

**JavaScript**

```javascript
const res = await fetch("https://api.youraiconnector.com/v1/faqs/apply-optimization", {
  method: "POST",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    campaignId: "campaign123",
    acceptedChanges: [
      { action: "create_new", question: "Do you ship to the EU?", answer: "Yes, EU shipping takes 7-10 business days." },
      { action: "remove", faq_ref_path: "users/abc123uid/faqs/oldFaqId" },
    ],
  }),
});
const data = await res.json();
```

**Python**

```python
import requests

res = requests.post(
    "https://api.youraiconnector.com/v1/faqs/apply-optimization",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "campaignId": "campaign123",
        "acceptedChanges": [
            {"action": "create_new", "question": "Do you ship to the EU?", "answer": "Yes, EU shipping takes 7-10 business days."},
            {"action": "remove", "faq_ref_path": "users/abc123uid/faqs/oldFaqId"},
        ],
    },
)
data = res.json()
```

**Phản hồi**

```json
{
  "success": true,
  "message": "Applied 2 FAQ changes",
  "faq_count": 7
}
```

`faq_count` là tổng số FAQ được liên kết của chiến dịch (hoặc Tác nhân) sau khi áp dụng. Nếu không có tập hợp thay đổi nào đang chờ xử lý để áp dụng, phản hồi sẽ là `{ "success": true, "message": "No pending FAQ changes to apply" }`.

### Tìm các FAQ tương tự với một tác vụ

`POST /faqs/similar-for-task`

Xếp hạng thư viện FAQ của bạn theo mức độ liên quan đến câu hỏi của tác vụ thiếu hụt kiến thức — đây cũng là cơ chế tra cứu đằng sau bộ chọn "Sử dụng FAQ hiện có" trên bảng điều khiển. Chỉ đọc. `taskId` phải trỏ đến một tác vụ thuộc loại `faq_update`.

Điểm cuối này luôn trả về `200`, ngay cả khi xảy ra lỗi dự kiến như tác vụ không xác định — hãy kiểm tra `success` trong phần nội dung thay vì trạng thái HTTP.

**Các trường yêu cầu**

| Trường | Bắt buộc | Mô tả |
|---|---|---|
| `taskId` | Có | Tác vụ `faq_update` cần tìm các kết quả khớp. |
| `limit` | Không | Số lượng kết quả khớp tối đa cần trả về. Mặc định là 20, giới hạn tối đa là 50. |

**cURL**

```bash
curl -X POST "https://api.youraiconnector.com/v1/faqs/similar-for-task?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "taskId": "task789", "limit": 10 }'
```

**JavaScript**

```javascript
const res = await fetch("https://api.youraiconnector.com/v1/faqs/similar-for-task", {
  method: "POST",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ taskId: "task789", limit: 10 }),
});
const data = await res.json();
```

**Python**

```python
import requests

res = requests.post(
    "https://api.youraiconnector.com/v1/faqs/similar-for-task",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={"taskId": "task789", "limit": 10},
)
data = res.json()
```

**Phản hồi**

```json
{
  "success": true,
  "data": {
    "task_id": "task789",
    "matches": [
      {
        "faq_id": "aBcD1234eFgH5678",
        "question": "How long does shipping take?",
        "answer": "Standard shipping takes 3-5 business days.",
        "category": "shipping",
        "created_at": "2026-01-01T12:00:00.000Z",
        "similarity": 0.81,
        "embedding_similarity": 0.81,
        "keyword_similarity": 0.6,
        "bm25_score": 4.2,
        "distance": 0.19
      }
    ]
  }
}
```

Các kết quả khớp được sắp xếp theo `similarity` (khớp ngữ nghĩa khi có sẵn, nếu không thì dựa trên sự trùng lặp từ khóa), kết quả tốt nhất đứng đầu. Khi xảy ra lỗi nhẹ, định dạng phản hồi là `{ "success": false, "error": "...", "error_code": 404 }` — `error_code` phản ánh trạng thái HTTP thông thường.

### Giải quyết tác vụ bằng FAQ hiện có

`POST /faqs/resolve-task`

Giải quyết tác vụ thiếu hụt kiến thức bằng cách liên kết nó với một FAQ bạn đã có (thay vì viết mới), gửi câu trả lời của FAQ đó cho liên hệ đã kích hoạt tác vụ thiếu hụt, và đánh dấu tác vụ là hoàn tất. Hãy sử dụng tính năng này sau khi [Tìm các FAQ tương tự với tác vụ](#find-faqs-similar-to-a-task) tìm thấy một FAQ hiện có đã bao hàm câu hỏi đó.

Giống như điểm cuối ở trên, điểm cuối này luôn trả về `200` — hãy kiểm tra `success` trong phần nội dung.

**Các trường yêu cầu**

| Trường | Bắt buộc | Mô tả |
|---|---|---|
| `taskId` | Có | Tác vụ `faq_update` cần giải quyết. |
| `faqId` | Có | FAQ hiện có cần liên kết và gửi làm câu trả lời. |

**cURL**

```bash
curl -X POST "https://api.youraiconnector.com/v1/faqs/resolve-task?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "taskId": "task789", "faqId": "aBcD1234eFgH5678" }'
```

**JavaScript**

```javascript
const res = await fetch("https://api.youraiconnector.com/v1/faqs/resolve-task", {
  method: "POST",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ taskId: "task789", faqId: "aBcD1234eFgH5678" }),
});
const data = await res.json();
```

**Python**

```python
import requests

res = requests.post(
    "https://api.youraiconnector.com/v1/faqs/resolve-task",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={"taskId": "task789", "faqId": "aBcD1234eFgH5678"},
)
data = res.json()
```

**Phản hồi**

```json
{
  "success": true,
  "data": {
    "task_id": "task789",
    "faq_id": "aBcD1234eFgH5678",
    "follow_up_status": "published"
  }
}
```

`follow_up_status` cho bạn biết điều gì đã xảy ra với việc theo dõi liên hệ: `published` (đã gửi ngay lập tức), `queued` (AI đang trong quá trình trả lời liên hệ đó, nên sẽ được gửi sau), `skipped_no_contact` (tác vụ không có liên hệ được liên kết), hoặc `skipped_no_campaign` (không có chiến dịch nào để gửi qua đó).

---

## Lỗi API FAQ

Các endpoint FAQ trả về cấu trúc lỗi tiêu chuẩn:

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

| Trạng thái | Khi nào xảy ra trên điểm cuối FAQ |
|---|---|
| `400` | Thiếu trường bắt buộc hoặc trường không hợp lệ (ví dụ: `question` trống, thiếu `campaign_id`, hoặc có hơn 500 mục trong một yêu cầu hàng loạt). |
| `404` | Không tìm thấy FAQ hoặc chiến dịch — hoặc là nó không tồn tại hoặc nó thuộc về tài khoản khác. |
| `409` | `POST /faqs/dedupe` đã được gọi trong khi công việc khử trùng lặp đang `queued`/`processing`, hoặc `POST /faqs/dedupe/dismiss` đã được gọi trong khi công việc chưa hoàn tất. |

Các mã chung mà mọi điểm cuối có thể trả về — `401`, `403` (gói của bạn không bao gồm quyền truy cập API), `429` (giới hạn tốc độ) và `500` — được liệt kê cùng với hướng dẫn thử lại trong [Lỗi & Phân trang](errors-and-pagination.md).

`POST /faqs/similar-for-task` và `POST /faqs/resolve-task` là hai ngoại lệ trên trang này: chúng trả về `200` ngay cả khi xảy ra lỗi dự kiến (tác vụ không xác định, sai loại tác vụ) và đặt trạng thái thực vào `error_code` trong phần nội dung — hãy xem từng điểm cuối ở trên.

---

## Liên quan

- [API Chiến dịch](campaigns.md) — các chiến dịch mà FAQ của bạn được liên kết tới.
- [API Cơ sở kiến thức](knowledge-base.md) — tự động nhập trang web và tài liệu vào FAQ, đồng thời gộp các FAQ thành các nhóm kiến thức có thể tái sử dụng.
- [Truy cập API](../integrations/api-access.md) — tạo khóa API của bạn.
- [Xác thực](authentication.md) — tất cả các cách để truyền khóa của bạn.
