
# FAQs API

FAQs are the question-and-answer entries your AI bot draws on when replying to customers. Each FAQ belongs to your account and can be linked to one or more campaigns, so the same answer can be reused everywhere it's relevant. The FAQs API lets you manage that library programmatically — create, update, bulk-import, reorder, and link FAQs to campaigns from your own code.

All endpoints below are relative to the base URL `https://api.youraiconnector.com/v1`. Every request must be authenticated — see [API Access](../integrations/api-access.md) and [Authentication](authentication.md). API access is a paid feature; without it, requests are rejected with a `403`.

> **How the bot uses a FAQ:** When you create or change a FAQ, the platform prepares its search data (used to match the FAQ to incoming questions) in the background. This usually finishes within a few seconds, after which the bot starts using the entry automatically.


---

## The FAQ object

Every FAQ that comes back from the API has this shape:

| Field | Type | Description |
|---|---|---|
| `id` | string | The FAQ's unique identifier. |
| `question` | string | The customer question this entry answers. |
| `answer` | string | The answer the AI bot gives. |
| `category` | string \| null | Optional free-form category label. |
| `tags` | string[] | Optional labels for organizing FAQs. |
| `is_active` | boolean | Whether the bot is allowed to use this FAQ. Defaults to `true`. |
| `is_global` | boolean | Marks the FAQ as not tied to one specific campaign or Agent. It does not make the FAQ apply everywhere: an FAQ is only used by the campaigns and Agents it is linked to. Defaults to `false`. |
| `usage_count` | integer | How many times this FAQ has been used in AI replies. |
| `order_index` | integer | Display position of this FAQ within its campaign. |
| `campaign_ids` | string[] | IDs of the campaigns this FAQ is linked to. |
| `created_at` | string \| null | ISO 8601 timestamp of when the FAQ was created. |
| `updated_at` | string \| null | ISO 8601 timestamp of the last change. |

The fields you can **set** are: `question`, `answer`, `is_active`, `is_global`, `category`, `tags`, and `order_index`. The platform manages everything else (search data, usage counts, timestamps); any other fields in your request body are ignored.

---

## List FAQs

`GET /faqs`

Returns the FAQs in your account, newest first. Optionally filter to a single campaign or by active state.

**Query parameters**

| Parameter | Required | Description |
|---|---|---|
| `campaign_id` | No | Only return FAQs linked to this campaign. |
| `is_active` | No | Only return FAQs with this active state (`true` or `false`). This filter is applied per page, so a page may contain fewer items than `limit`. |
| `limit` | No | Maximum FAQs per page. Default `50`, maximum `100`. |
| `cursor` | No | A FAQ ID to continue after. Pass the `next_cursor` value from the previous page. |

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

**Response**

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

When `next_cursor` is `null`, there are no more results.

---

## Get a FAQ

`GET /faqs/{faqId}`

Returns a single FAQ by its ID.

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

**Response**

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

---

## Create a FAQ

`POST /faqs`

Creates a new FAQ and links it to a campaign.

**Request fields**

| Field | Required | Description |
|---|---|---|
| `campaign_id` | Yes | The campaign to link the new FAQ to. |
| `question` | Yes | The customer question this entry answers. |
| `answer` | Yes | The answer the bot should give. |
| `is_active` | No | Whether the bot may use this FAQ. Defaults to `true`. |
| `is_global` | No | Whether the FAQ applies to all campaigns. Defaults to `false`. |
| `category` | No | A free-form category label. |
| `tags` | No | An array of labels. |
| `order_index` | No | Display position within the campaign. Defaults to `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"]
```

**Response**

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

---

## Update a FAQ

`PUT /faqs/{faqId}`

Partially updates a FAQ. Only the supplied writable fields are changed; everything else keeps its current value. Changing the `question` or `answer` automatically refreshes the FAQ's search data in the background.

If you send `question` or `answer`, they must be non-empty strings. Sending no recognized writable fields returns a `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()
```

**Response**

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

---

## Delete a FAQ

`DELETE /faqs/{faqId}`

Permanently deletes a FAQ. Optionally pass `campaign_id` as a query parameter to also remove the FAQ from that campaign's FAQ list.

**Query parameters**

| Parameter | Required | Description |
|---|---|---|
| `campaign_id` | No | Also remove the FAQ from this campaign's FAQ list. |

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

**Response**

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

---

## Bulk-delete FAQs

`POST /faqs/bulk-delete`

Deletes up to 500 FAQs in a single request. When `campaign_id` is supplied, the deleted FAQs are also removed from that campaign's FAQ list.

**Request fields**

| Field | Required | Description |
|---|---|---|
| `faq_ids` | Yes | A non-empty array of FAQ IDs to delete (max 500). |
| `campaign_id` | No | Also remove the deleted FAQs from this campaign's FAQ list. |

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

**Response**

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

---

## Import FAQs

`POST /faqs/import`

Bulk-imports up to 500 FAQs and links them all to one campaign. Items whose `question` matches an existing FAQ in your library (case-insensitive) **update** that FAQ instead of creating a duplicate.

> **Performance tip:** Duplicate matching scans your whole FAQ library, so very large libraries make imports slower. Prefer fewer, larger imports over many small ones.

**Request fields**

| Field | Required | Description |
|---|---|---|
| `campaign_id` | Yes | The campaign all imported FAQs are linked to. |
| `faqs` | Yes | A non-empty array of FAQ items (max 500). Each item must have a non-empty `question` and `answer`; it may also include `is_active`, `is_global`, `category`, `tags`, and `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()
```

**Response**

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

`faq_ids` are the created or updated FAQ IDs, in the order you supplied them.

---

## Reorder FAQs

`POST /faqs/reorder`

Sets the display order of a campaign's FAQs. Supply the **full** list of FAQ IDs in the desired order; each FAQ's position is updated to match its place in the array.

**Request fields**

| Field | Required | Description |
|---|---|---|
| `campaign_id` | Yes | The campaign whose FAQs are being reordered. |
| `ordered_faq_ids` | Yes | A non-empty array of all the campaign's FAQ IDs in the desired display order (max 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()
```

**Response**

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

If the campaign or any of the FAQ IDs isn't found in your account, the request returns `404 One or more FAQs were not found`.

---

## Link a FAQ to a campaign

`POST /faqs/{faqId}/link`

Links an existing FAQ to an additional campaign. A FAQ can be shared by any number of campaigns, so the same answer only needs to be maintained once.

**Request fields**

| Field | Required | Description |
|---|---|---|
| `campaign_id` | Yes | The campaign to link the FAQ to. |

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

**Response**

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

---

## Unlink a FAQ from a campaign

`POST /faqs/{faqId}/unlink`

Removes a FAQ from a campaign without deleting the FAQ itself. The FAQ stays in your library and remains linked to any other campaigns.

**Request fields**

| Field | Required | Description |
|---|---|---|
| `campaign_id` | Yes | The campaign to remove the FAQ from. |

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

**Response**

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

---

## Rebuild a FAQ's search data

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

Queues a rebuild of the data the AI bot uses to find this FAQ (its semantic and keyword search data). This is useful if a FAQ isn't being picked up in replies as expected. The rebuild runs in the background and usually completes within a few seconds; the FAQ may be temporarily excluded from AI replies while it is being rebuilt.

This endpoint returns `202 Accepted` because the work continues after the response is sent. The `status` is always `"processing"` — re-fetch the FAQ later if you need to confirm completion.

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

**Response**

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

---

## AI-assisted FAQ management

The endpoints below go beyond plain CRUD: they call the same AI-assist tools the dashboard's FAQ editor uses — finding duplicates, generating entries from a document, and matching FAQs to open knowledge-gap tasks. Request bodies on this set use `camelCase` field names (`campaignId`, `taskId`, `sourceIds`...), matching the app's own request shapes, rather than the `snake_case` used elsewhere on this page — copy the examples below rather than guessing a field name.

### Fork a FAQ into a campaign-only copy

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

Creates a new FAQ that's a copy of an existing one, scoped to a single campaign, and re-links that campaign to the new copy instead of the original. Use this when you want to customize an answer for one campaign without changing it everywhere else the original FAQ is used. The original FAQ is left in place — it only loses this campaign's link.

**Request fields**

| Field | Required | Description |
|---|---|---|
| `campaign_id` | Yes | The campaign to scope the new copy to, and to re-link from the original FAQ. |
| `question` | Yes | The question for the new, campaign-specific copy. |
| `answer` | Yes | The answer for the new, campaign-specific copy. |

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

**Response** — `201 Created`

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

### Find near-duplicate FAQs

`POST /faqs/dedupe`

Starts a background job that scans your FAQ library for near-duplicate and overlapping entries and merges or removes them where it's confident. Useful after a bulk import, or after several rounds of AI-generated FAQs have left the library with overlap. Only one dedupe job can run per account at a time — starting a second one while a job is still running returns `409`.

**Request fields**

| Field | Required | Description |
|---|---|---|
| `sourceIds` | No | Array of knowledge-base source IDs to scope the dedupe to. Omit to scan your whole FAQ library. |

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

**Response** — `202 Accepted`

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

The job runs in the background and typically takes a few minutes on a large library. There's no separate status endpoint — re-fetch [`GET /faqs`](#list-faqs) after a short wait to see what changed. When you're done reviewing the result, call the dismiss endpoint below to clear it.

### Dismiss a duplicate-check result

`POST /faqs/dedupe/dismiss`

Clears the finished dedupe job so it stops showing as an active result. Idempotent — safe to call even if there's nothing to dismiss. Returns `409` if the job is still `queued` or `processing` (you can't dismiss a run that hasn't finished).

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

**Response**

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

### Generate FAQs from uploaded documents

`POST /faqs/generate-from-documents`

Reads one or more documents already in your account's file storage and has the AI draft FAQs from their content, checking the drafts against your existing library so it reuses or updates entries instead of creating duplicates. Results are **not** written immediately — they're stored as a pending change set on the campaign for you to review, then applied (or discarded) with [Apply reviewed FAQ changes](#apply-reviewed-faq-changes) below. This costs credits, since it's an AI generation pass over the document text.

This endpoint doesn't carry the file: `storagePath` must point to a file already under your own uploads folder (`users/{your user id}/uploads/`), the same convention as [Import an uploaded document](knowledge-base.md#import-an-uploaded-document) on the Knowledge Base API.

**Request fields**

| Field | Required | Description |
|---|---|---|
| `campaignId` | Yes | The campaign the generated FAQs are proposed for. |
| `uploadedFiles` | Yes | Non-empty array of files to read, each `{ storagePath, fileName, mimeType }`. `storagePath` must start with `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()
```

**Response** — `202 Accepted`

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

`faqCount` is the total number of proposed changes waiting for review; `reusedCount`, `modifiedCount` and `newCount` break that down into FAQs that matched an existing entry unchanged, ones the AI proposes editing, and brand-new ones. Uploaded files are deleted from storage once processing finishes, whether or not it succeeds.

### Apply reviewed FAQ changes

`POST /faqs/apply-optimization`

Applies (or discards) a pending set of AI-proposed FAQ changes — the kind produced by [Generate FAQs from documents](#generate-faqs-from-uploaded-documents) above, or by the dashboard's FAQ optimization review. You choose exactly which proposed changes to accept; anything you don't mention is left untouched (an omitted change is never treated as a rejection that deletes something).

**Request fields**

| Field | Required | Description |
|---|---|---|
| `campaignId` | One of these two | The campaign whose pending FAQ changes are being applied. |
| `agentId` | One of these two | The AI Agent whose pending FAQ changes are being applied, on an agent-native account. Supply exactly one of `campaignId` / `agentId`, never both. |
| `acceptedChanges` | Yes | Array of the changes you accept, each `{ action, faq_id?, faq_ref_path?, question?, answer?, edit_scope? }`. `action` is one of `keep`, `remove`, `add_from_library`, `create_new`, `modify`. Send an empty array to discard the pending set without applying anything. |

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

**Response**

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

`faq_count` is the campaign's (or Agent's) total linked FAQ count after applying. If there was no pending change set to apply, the response is `{ "success": true, "message": "No pending FAQ changes to apply" }`.

### Find FAQs similar to a task

`POST /faqs/similar-for-task`

Ranks your FAQ library by relevance to a knowledge-gap task's question — the same lookup behind the dashboard's "Use an existing FAQ" picker. Read-only. `taskId` must point to a task of type `faq_update`.

This endpoint always answers `200`, even on an expected failure like an unknown task — check `success` in the body rather than the HTTP status.

**Request fields**

| Field | Required | Description |
|---|---|---|
| `taskId` | Yes | The `faq_update` task to find matches for. |
| `limit` | No | Maximum matches to return. Defaults to 20, capped at 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()
```

**Response**

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

Matches are sorted by `similarity` (semantic match when available, keyword overlap otherwise), best first. On a soft failure the shape is `{ "success": false, "error": "...", "error_code": 404 }` — `error_code` mirrors what the HTTP status would normally be.

### Resolve a task with an existing FAQ

`POST /faqs/resolve-task`

Resolves a knowledge-gap task by linking it to an FAQ you already have (instead of writing a new one), sends that FAQ's answer to the contact who triggered the gap, and marks the task complete. Use this after [Find FAQs similar to a task](#find-faqs-similar-to-a-task) turns up an existing FAQ that already covers the question.

Like the endpoint above, this always answers `200` — check `success` in the body.

**Request fields**

| Field | Required | Description |
|---|---|---|
| `taskId` | Yes | The `faq_update` task to resolve. |
| `faqId` | Yes | The existing FAQ to link and send as the answer. |

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

**Response**

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

`follow_up_status` tells you what happened to the contact follow-up: `published` (sent right away), `queued` (the AI was already mid-reply to that contact, so it'll go out next), `skipped_no_contact` (the task has no linked contact), or `skipped_no_campaign` (no campaign to send it through).

---

## FAQs API errors

FAQ endpoints return the standard error envelope:

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

| Status | When it happens on an FAQ endpoint |
|---|---|
| `400` | A required field is missing or invalid (for example an empty `question`, a missing `campaign_id`, or more than 500 items in a bulk request). |
| `404` | The FAQ or campaign was not found — either it doesn't exist or it belongs to another account. |
| `409` | `POST /faqs/dedupe` was called while a dedupe job is already `queued`/`processing`, or `POST /faqs/dedupe/dismiss` was called while the job hasn't finished yet. |

The shared codes every endpoint can return — `401`, `403` (your plan does not include API access), `429` (rate limit) and `500` — are listed with retry guidance in [Errors & Pagination](errors-and-pagination.md).

`POST /faqs/similar-for-task` and `POST /faqs/resolve-task` are the two exceptions on this page: they answer `200` even for an expected failure (unknown task, wrong task type) and put the real status in the body's `error_code` instead — see each endpoint above.

---

## Related

- [Campaigns API](campaigns.md) — the campaigns your FAQs are linked to.
- [Knowledge Base API](knowledge-base.md) — import websites and documents into FAQs automatically, and bundle FAQs into reusable knowledge groups.
- [API Access](../integrations/api-access.md) — generate your API key.
- [Authentication](authentication.md) — all the ways to pass your key.
