# Training Data

## Search existing knowledge for a passage

`client.trainingData.search(TrainingDataSearchParamsparams, RequestOptionsoptions?): KnowledgeSearch`

**post** `/v2/training_data/search`

Searches one passage or claim across Q&A, training files, and live help-center articles. Returns ranked candidates and query-focused excerpts without judging or writing. Similarity is an embedding score, not factual confidence; calibrate thresholds on representative examples and retain keyword-only results (similarity null). Retrieval and excerpts can miss relevant facts. Fetch full sources for decisions. Split long documents into bounded sections and audit each section before upload. Latency and embedding work depend on query size and service load.

### Parameters

- `params: TrainingDataSearchParams`

  - `query: string`

    Body param: A passage, claim, or question to find existing knowledge for — for example one section of a document you are about to upload. Compared by meaning and by keywords.

  - `excludeIds?: Array<string>`

    Body param: Q&A entry / training file IDs to leave out — typically the item you are about to update.

  - `kinds?: Array<"qna" | "training_file" | "help_center_article">`

    Body param: Restrict to some kinds of knowledge. Default: all three (Q&A entries, training files, live help-center articles).

    - `"qna"`

    - `"training_file"`

    - `"help_center_article"`

  - `topK?: number`

    Body param: How many items to return (1–20).

  - `featurebaseVersion?: "2026-08-19.orbit" | "2026-01-01.nova" | "2025-12-12.clover"`

    Header param: API version for this request. Defaults to your organization's configured API version if not specified.

    - `"2026-08-19.orbit"`

    - `"2026-01-01.nova"`

    - `"2025-12-12.clover"`

### Returns

- `KnowledgeSearch`

  - `candidates: Array<KnowledgeMatchCandidate>`

    Existing items closest to the query, best first, at most `topK`. `text` is an excerpt focused on the query; `section` is the heading path inside a file when known.

    - `id: string`

      ID of the existing item

    - `externalId: string | null`

      The item's source identity, for training files and Q&A

    - `kind: "qna" | "training_file" | "help_center_article"`

      What kind of knowledge item matched: a Q&A entry, a training file / crawled page, or a live help-center article.

      - `"qna"`

      - `"training_file"`

      - `"help_center_article"`

    - `questions: Array<string> | null`

      Question variants, for Q&A entries

    - `revision: number | null`

      Q&A revision compared by the judge; null for other kinds. Use with PATCH expectedRevision.

    - `section: string | null`

      Heading breadcrumb of the matching section, for training files

    - `similarity: number | null`

      Cosine similarity between the query and item. Calibrate any threshold using representative examples; it is not a correctness or contradiction score. `null` when the item was found by keywords only. Arrays are already ordered closest first.

    - `source: string | null`

      The item's `source` label, for Q&A entries and training files

    - `text: string`

      Excerpt of the existing content the judge compared against (Q&A answer, matching document section, or article body), up to 1200 characters plus an ellipsis when cut.

    - `title: string`

      Title of the existing item

    - `url: string | null`

      Public URL of the item when it has one

  - `object: "knowledge_search"`

    Object type identifier

    - `"knowledge_search"`

  - `query: string`

    The query as searched (trimmed)

  - `timings: Timings`

    Where the time went

    - `retrievalMs: number`

      Time spent searching and loading the items

### Example

```typescript
import Featurebase from 'featurebase-node';

const client = new Featurebase({
  apiKey: process.env['FEATUREBASE_API_KEY'], // This is the default and can be omitted
});

const knowledgeSearch = await client.trainingData.search({
  query: 'Refunds are processed within 14 days of the request.',
});

console.log(knowledgeSearch.candidates);
```

#### Response

```json
{
  "candidates": [
    {
      "id": "67ec1234abcd5678ef901236",
      "externalId": null,
      "kind": "qna",
      "questions": [
        "How do I request a refund?"
      ],
      "revision": 1,
      "section": null,
      "similarity": 0.61,
      "source": null,
      "text": "You can request a refund within 30 days from **Settings → Billing**.",
      "title": "Refund requests",
      "url": null
    }
  ],
  "object": "knowledge_search",
  "query": "Refunds are processed within 14 days of the request.",
  "timings": {
    "retrievalMs": 690
  }
}
```

## Ask what to do with a Q&A entry

`client.trainingData.oracle(TrainingDataOracleParamsparams, RequestOptionsoptions?): Oracle`

**post** `/v2/training_data/oracle`

Checks a proposed Q&A without writing. Optional externalId or an existing normalized question addresses a target. Changed content is compared with that entry even if retrieval or excludeIds misses it. The judge continues past agreement to detect later contradictions; uncertainty takes precedence over agreement. Another agreeing Q&A remains visible for duplicate review.

Actions: create means no overlap found in checked candidates; update_target refers to existing; update_qna refers to target; review_qna / review_article / review_file require review of the source; skip means an identical payload or already-covered answer. existing and Q&A candidates include revisions: use PATCH expectedRevision for a subsequent reviewed update.

The evidence is bounded: retrieval can miss knowledge, document excerpts omit context, and checks observe changing data. checkTruncated reports verification shortlist or text limits and yields unclear unless a contradiction was found. False does not imply exhaustive coverage or factual correctness. Inspect full sources before promotion. Dependencies failing returns 503; this endpoint never writes.

### Parameters

- `params: TrainingDataOracleParams`

  - `answer: string`

    Body param: The answer the entry would give. Compared by the judge, never used as a search query.

  - `questions: Array<string>`

    Body param: Question variants this answer applies to (1–50). Phrase them the way customers ask.

  - `answerFormat?: "markdown" | "html"`

    Body param: Format of `answer`. `markdown` (default) is stored as-is; `html` is converted to markdown.

    - `"markdown"`

    - `"html"`

  - `excludeIds?: Array<string>`

    Body param: Q&A entry / training file IDs to omit from retrieval. The addressed Q&A entry is still compared when its content changes.

  - `externalId?: string`

    Body param: Stable identity of the would-be Q&A entry. The oracle only reads; it never creates or updates it.

  - `title?: string`

    Body param: Short label for the would-be entry (also used as a search query)

  - `featurebaseVersion?: "2026-08-19.orbit" | "2026-01-01.nova" | "2025-12-12.clover"`

    Header param: API version for this request. Defaults to your organization's configured API version if not specified.

    - `"2026-08-19.orbit"`

    - `"2026-01-01.nova"`

    - `"2025-12-12.clover"`

### Returns

- `Oracle`

  - `action: "create" | "update_target" | "update_qna" | 4 more`

    Suggested next step. `create`: no overlap found in the checked candidates; verify the source before writing. `update_target`: an entry already carries one of these questions (`existing`) and should be updated with this content — use PATCH with expectedRevision from existing to bind the write to this check. `update_qna`: an existing Q&A entry already answers this with the same facts — merge into it (`target`) instead of adding a second one. `review_qna` / `review_article` / `review_file`: an existing item (`target`) contradicts this answer or the judge could not decide — a human should pick the right fact and fix the source. `skip`: the knowledge already exists (an article or file answers it, or the payload is identical to the entry that already carries the question); nothing to write.

    - `"create"`

    - `"update_target"`

    - `"update_qna"`

    - `"review_qna"`

    - `"review_article"`

    - `"review_file"`

    - `"skip"`

  - `candidates: Array<KnowledgeMatchCandidate>`

    Retrieved items and the addressed entry (first when present), at most 10. Excerpts are partial evidence.

    - `id: string`

      ID of the existing item

    - `externalId: string | null`

      The item's source identity, for training files and Q&A

    - `kind: "qna" | "training_file" | "help_center_article"`

      What kind of knowledge item matched: a Q&A entry, a training file / crawled page, or a live help-center article.

      - `"qna"`

      - `"training_file"`

      - `"help_center_article"`

    - `questions: Array<string> | null`

      Question variants, for Q&A entries

    - `revision: number | null`

      Q&A revision compared by the judge; null for other kinds. Use with PATCH expectedRevision.

    - `section: string | null`

      Heading breadcrumb of the matching section, for training files

    - `similarity: number | null`

      Cosine similarity between the query and item. Calibrate any threshold using representative examples; it is not a correctness or contradiction score. `null` when the item was found by keywords only. Arrays are already ordered closest first.

    - `source: string | null`

      The item's `source` label, for Q&A entries and training files

    - `text: string`

      Excerpt of the existing content the judge compared against (Q&A answer, matching document section, or article body), up to 1200 characters plus an ellipsis when cut.

    - `title: string`

      Title of the existing item

    - `url: string | null`

      Public URL of the item when it has one

  - `checkTruncated: boolean`

    A candidate or text budget limited verification (including clipped proposed answers or Q&A answers). This yields unclear unless a contradiction was already found. False does not mean the entire workspace was exhaustively checked.

  - `existing: Existing | null`

    The Q&A entry addressed by externalId or a normalized question, including its checked revision, or null

    - `id: string`

      Q&A entry ID

    - `question: string`

      Matching question from your request, or its first question when addressed by externalId

    - `revision: number`

      Revision of the addressed entry at check time. Use with PATCH expectedRevision.

  - `judged: Array<Judged>`

    Per-candidate judge verdicts, in the order they were judged. Judging continues after agreement or uncertainty and stops on a contradiction. The addressed entry is always compared when its content changes. Only shortlisted candidates are judged, so this is usually shorter than `candidates`.

    - `candidateId: string`

      ID of the judged candidate

    - `kind: "qna" | "training_file" | "help_center_article"`

      What kind of knowledge item matched: a Q&A entry, a training file / crawled page, or a live help-center article.

      - `"qna"`

      - `"training_file"`

      - `"help_center_article"`

    - `reason: string`

      The judge's one-line explanation

    - `verdict: "same_topic" | "contradicts" | "different" | "unclear"`

      The judge's verdict for this candidate

      - `"same_topic"`

      - `"contradicts"`

      - `"different"`

      - `"unclear"`

  - `object: "oracle"`

    Object type identifier

    - `"oracle"`

  - `reason: string`

    One or two sentences explaining the action, from the judge when it ran

  - `target: Target`

    The existing item the action refers to, with its excerpt; `null` for `create` / `update_target`. For a `different` verdict the closest miss is `candidates[0]`.

  - `timings: Timings`

    Where the time went

    - `judgeCalls: number`

      Number of judge calls made

    - `judgeMs: number`

      Time spent in the judge

    - `retrievalMs: number`

      Time spent searching

  - `verdict: "same_topic" | "contradicts" | "different" | 2 more`

    How the closest existing item relates to the entry. `same_topic`: it answers the same customer question (the entry would be a duplicate, rewording, or updated version). `contradicts`: same question, incompatible facts. `different`: a different question, even if the wording overlaps. `unclear`: the judge could not decide. `no_match`: nothing similar was found.

    - `"same_topic"`

    - `"contradicts"`

    - `"different"`

    - `"unclear"`

    - `"no_match"`

### Example

```typescript
import Featurebase from 'featurebase-node';

const client = new Featurebase({
  apiKey: process.env['FEATUREBASE_API_KEY'], // This is the default and can be omitted
});

const oracle = await client.trainingData.oracle({
  answer: 'You can request a refund within 30 days from **Settings → Billing**.',
  questions: ['How do I request a refund?', 'Can I get my money back?'],
});

console.log(oracle.candidates);
```

#### Response

```json
{
  "action": "review_article",
  "candidates": [
    {
      "id": "67ec1234abcd5678ef901236",
      "externalId": null,
      "kind": "qna",
      "questions": [
        "How do I request a refund?"
      ],
      "revision": 1,
      "section": null,
      "similarity": 0.61,
      "source": null,
      "text": "You can request a refund within 30 days from **Settings → Billing**.",
      "title": "Refund requests",
      "url": null
    }
  ],
  "checkTruncated": true,
  "existing": {
    "id": "67ec1234abcd5678ef901234",
    "question": "How long do refunds take?",
    "revision": 1
  },
  "judged": [
    {
      "candidateId": "67ec1234abcd5678ef901236",
      "kind": "qna",
      "reason": "Both answer how to request a refund.",
      "verdict": "same_topic"
    }
  ],
  "object": "oracle",
  "reason": "The new entry says 14 days; the article says 30 days.",
  "target": {
    "id": "67ec1234abcd5678ef901236",
    "externalId": null,
    "kind": "qna",
    "questions": [
      "How do I request a refund?"
    ],
    "revision": 1,
    "section": null,
    "similarity": 0.61,
    "source": null,
    "text": "You can request a refund within 30 days from **Settings → Billing**.",
    "title": "Refund requests",
    "url": null
  },
  "timings": {
    "judgeCalls": 1,
    "judgeMs": 610,
    "retrievalMs": 420
  },
  "verdict": "same_topic"
}
```

## Domain Types

### Knowledge Match Candidate

- `KnowledgeMatchCandidate`

  - `id: string`

    ID of the existing item

  - `externalId: string | null`

    The item's source identity, for training files and Q&A

  - `kind: "qna" | "training_file" | "help_center_article"`

    What kind of knowledge item matched: a Q&A entry, a training file / crawled page, or a live help-center article.

    - `"qna"`

    - `"training_file"`

    - `"help_center_article"`

  - `questions: Array<string> | null`

    Question variants, for Q&A entries

  - `revision: number | null`

    Q&A revision compared by the judge; null for other kinds. Use with PATCH expectedRevision.

  - `section: string | null`

    Heading breadcrumb of the matching section, for training files

  - `similarity: number | null`

    Cosine similarity between the query and item. Calibrate any threshold using representative examples; it is not a correctness or contradiction score. `null` when the item was found by keywords only. Arrays are already ordered closest first.

  - `source: string | null`

    The item's `source` label, for Q&A entries and training files

  - `text: string`

    Excerpt of the existing content the judge compared against (Q&A answer, matching document section, or article body), up to 1200 characters plus an ellipsis when cut.

  - `title: string`

    Title of the existing item

  - `url: string | null`

    Public URL of the item when it has one

### Knowledge Search

- `KnowledgeSearch`

  - `candidates: Array<KnowledgeMatchCandidate>`

    Existing items closest to the query, best first, at most `topK`. `text` is an excerpt focused on the query; `section` is the heading path inside a file when known.

    - `id: string`

      ID of the existing item

    - `externalId: string | null`

      The item's source identity, for training files and Q&A

    - `kind: "qna" | "training_file" | "help_center_article"`

      What kind of knowledge item matched: a Q&A entry, a training file / crawled page, or a live help-center article.

      - `"qna"`

      - `"training_file"`

      - `"help_center_article"`

    - `questions: Array<string> | null`

      Question variants, for Q&A entries

    - `revision: number | null`

      Q&A revision compared by the judge; null for other kinds. Use with PATCH expectedRevision.

    - `section: string | null`

      Heading breadcrumb of the matching section, for training files

    - `similarity: number | null`

      Cosine similarity between the query and item. Calibrate any threshold using representative examples; it is not a correctness or contradiction score. `null` when the item was found by keywords only. Arrays are already ordered closest first.

    - `source: string | null`

      The item's `source` label, for Q&A entries and training files

    - `text: string`

      Excerpt of the existing content the judge compared against (Q&A answer, matching document section, or article body), up to 1200 characters plus an ellipsis when cut.

    - `title: string`

      Title of the existing item

    - `url: string | null`

      Public URL of the item when it has one

  - `object: "knowledge_search"`

    Object type identifier

    - `"knowledge_search"`

  - `query: string`

    The query as searched (trimmed)

  - `timings: Timings`

    Where the time went

    - `retrievalMs: number`

      Time spent searching and loading the items

### Oracle

- `Oracle`

  - `action: "create" | "update_target" | "update_qna" | 4 more`

    Suggested next step. `create`: no overlap found in the checked candidates; verify the source before writing. `update_target`: an entry already carries one of these questions (`existing`) and should be updated with this content — use PATCH with expectedRevision from existing to bind the write to this check. `update_qna`: an existing Q&A entry already answers this with the same facts — merge into it (`target`) instead of adding a second one. `review_qna` / `review_article` / `review_file`: an existing item (`target`) contradicts this answer or the judge could not decide — a human should pick the right fact and fix the source. `skip`: the knowledge already exists (an article or file answers it, or the payload is identical to the entry that already carries the question); nothing to write.

    - `"create"`

    - `"update_target"`

    - `"update_qna"`

    - `"review_qna"`

    - `"review_article"`

    - `"review_file"`

    - `"skip"`

  - `candidates: Array<KnowledgeMatchCandidate>`

    Retrieved items and the addressed entry (first when present), at most 10. Excerpts are partial evidence.

    - `id: string`

      ID of the existing item

    - `externalId: string | null`

      The item's source identity, for training files and Q&A

    - `kind: "qna" | "training_file" | "help_center_article"`

      What kind of knowledge item matched: a Q&A entry, a training file / crawled page, or a live help-center article.

      - `"qna"`

      - `"training_file"`

      - `"help_center_article"`

    - `questions: Array<string> | null`

      Question variants, for Q&A entries

    - `revision: number | null`

      Q&A revision compared by the judge; null for other kinds. Use with PATCH expectedRevision.

    - `section: string | null`

      Heading breadcrumb of the matching section, for training files

    - `similarity: number | null`

      Cosine similarity between the query and item. Calibrate any threshold using representative examples; it is not a correctness or contradiction score. `null` when the item was found by keywords only. Arrays are already ordered closest first.

    - `source: string | null`

      The item's `source` label, for Q&A entries and training files

    - `text: string`

      Excerpt of the existing content the judge compared against (Q&A answer, matching document section, or article body), up to 1200 characters plus an ellipsis when cut.

    - `title: string`

      Title of the existing item

    - `url: string | null`

      Public URL of the item when it has one

  - `checkTruncated: boolean`

    A candidate or text budget limited verification (including clipped proposed answers or Q&A answers). This yields unclear unless a contradiction was already found. False does not mean the entire workspace was exhaustively checked.

  - `existing: Existing | null`

    The Q&A entry addressed by externalId or a normalized question, including its checked revision, or null

    - `id: string`

      Q&A entry ID

    - `question: string`

      Matching question from your request, or its first question when addressed by externalId

    - `revision: number`

      Revision of the addressed entry at check time. Use with PATCH expectedRevision.

  - `judged: Array<Judged>`

    Per-candidate judge verdicts, in the order they were judged. Judging continues after agreement or uncertainty and stops on a contradiction. The addressed entry is always compared when its content changes. Only shortlisted candidates are judged, so this is usually shorter than `candidates`.

    - `candidateId: string`

      ID of the judged candidate

    - `kind: "qna" | "training_file" | "help_center_article"`

      What kind of knowledge item matched: a Q&A entry, a training file / crawled page, or a live help-center article.

      - `"qna"`

      - `"training_file"`

      - `"help_center_article"`

    - `reason: string`

      The judge's one-line explanation

    - `verdict: "same_topic" | "contradicts" | "different" | "unclear"`

      The judge's verdict for this candidate

      - `"same_topic"`

      - `"contradicts"`

      - `"different"`

      - `"unclear"`

  - `object: "oracle"`

    Object type identifier

    - `"oracle"`

  - `reason: string`

    One or two sentences explaining the action, from the judge when it ran

  - `target: Target`

    The existing item the action refers to, with its excerpt; `null` for `create` / `update_target`. For a `different` verdict the closest miss is `candidates[0]`.

  - `timings: Timings`

    Where the time went

    - `judgeCalls: number`

      Number of judge calls made

    - `judgeMs: number`

      Time spent in the judge

    - `retrievalMs: number`

      Time spent searching

  - `verdict: "same_topic" | "contradicts" | "different" | 2 more`

    How the closest existing item relates to the entry. `same_topic`: it answers the same customer question (the entry would be a duplicate, rewording, or updated version). `contradicts`: same question, incompatible facts. `different`: a different question, even if the wording overlaps. `unclear`: the judge could not decide. `no_match`: nothing similar was found.

    - `"same_topic"`

    - `"contradicts"`

    - `"different"`

    - `"unclear"`

    - `"no_match"`

# Files

## List training files

`client.trainingData.files.list(FileListParamsparams?, RequestOptionsoptions?): CursorPage<TrainingFile>`

**get** `/v2/training_data/files`

Returns uploaded training files, newest first, with cursor pagination. Filter by `externalId` (exact) or `source`. The extracted text is omitted from list responses — compare `contentHash` to detect changes, and retrieve a single file to read it.

### Parameters

- `params: FileListParams`

  - `cursor?: string`

    Query param: An opaque cursor for pagination. Use the nextCursor value from a previous response to fetch the next page of results.

  - `externalId?: string`

    Query param: Only return the entry with this `externalId`

  - `limit?: number`

    Query param: A limit on the number of objects to be returned, between 1 and 100.

  - `source?: string`

    Query param: Only return entries with this `source` label

  - `status?: "processing" | "completed" | "failed"`

    Query param: Only return files in this processing state

    - `"processing"`

    - `"completed"`

    - `"failed"`

  - `featurebaseVersion?: "2026-08-19.orbit" | "2026-01-01.nova" | "2025-12-12.clover"`

    Header param: API version for this request. Defaults to your organization's configured API version if not specified.

    - `"2026-08-19.orbit"`

    - `"2026-01-01.nova"`

    - `"2025-12-12.clover"`

### Returns

- `TrainingFile`

  - `id: string`

    Training file ID

  - `characterCount: number | null`

    Characters of extracted text

  - `contentHash: string | null`

    SHA-256 (hex) of the stored source: for inline text and content edits the trimmed text, for uploads and URLs the original bytes. Compare it with your own hash to detect changes without downloading `content`.

  - `contentPreview: string | null`

    First 200 characters of the extracted text

  - `createdAt: string`

    ISO timestamp of the upload

  - `externalId: string | null`

    Your own identifier for this file, if one was set

  - `fileSize: number | null`

    Size of the originally uploaded source in bytes (unchanged by content edits)

  - `indexStatus: "pending" | "indexed" | "failed"`

    Whether the content is searchable by the AI agent. `pending` while indexing, `indexed` when the AI agent can use it, `failed` if indexing failed (the content is stored and indexing is retried hourly).

    - `"pending"`

    - `"indexed"`

    - `"failed"`

  - `mimeType: string | null`

    MIME type of the originally uploaded source

  - `name: string`

    File name shown in the dashboard

  - `object: "training_file"`

    Object type identifier

    - `"training_file"`

  - `processingError: string | null`

    Conversion error message when `status` is `failed`

  - `source: string | null`

    Pipeline label, if one was set

  - `status: "processing" | "completed" | "failed"`

    `processing` while the file is being converted to text, `completed` once the content is stored, `failed` if conversion failed (see `processingError`).

    - `"processing"`

    - `"completed"`

    - `"failed"`

  - `updatedAt: string`

    ISO timestamp of the last change

  - `wordCount: number | null`

    Words of extracted text

  - `content?: string | null`

    Full extracted text (markdown). Returned when retrieving or updating a single file; omitted from list and create responses.

  - `outcome?: "created" | "updated" | "unchanged"`

    On create and update responses: `created`, `updated` (same `externalId` or same name and bytes, content replaced and re-indexed), or `unchanged` (identical content, nothing done). Absent on reads.

    - `"created"`

    - `"updated"`

    - `"unchanged"`

### Example

```typescript
import Featurebase from 'featurebase-node';

const client = new Featurebase({
  apiKey: process.env['FEATUREBASE_API_KEY'], // This is the default and can be omitted
});

// Automatically fetches more pages as needed.
for await (const trainingFile of client.trainingData.files.list()) {
  console.log(trainingFile.id);
}
```

#### Response

```json
{
  "data": [
    {
      "id": "67ec1234abcd5678ef901234",
      "characterCount": 12840,
      "contentHash": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
      "contentPreview": "# Refund policy\n\nCustomers can request a refund within 30 days…",
      "createdAt": "2026-09-03T10:15:00.000Z",
      "externalId": "kb-article-1842",
      "fileSize": 48213,
      "indexStatus": "indexed",
      "mimeType": "application/pdf",
      "name": "refund-policy.pdf",
      "object": "training_file",
      "processingError": null,
      "source": "resolved-conversations",
      "status": "completed",
      "updatedAt": "2026-09-03T10:15:30.000Z",
      "wordCount": 2130,
      "content": "# Refund policy\n\nCustomers can request a refund within 30 days of purchase…",
      "outcome": "created"
    }
  ],
  "nextCursor": "eyJpZCI6IjUwN2YxZjc3YmNmODZjZDc5OTQzOTAxMSJ9",
  "object": "list"
}
```

## Add a training file

`client.trainingData.files.create(FileCreateParamsparams?, RequestOptionsoptions?): TrainingFile`

**post** `/v2/training_data/files`

Adds a document the AI agent can learn from. Provide exactly one source:

- **Inline text** — `{ "name": "refund-policy.md", "content": "..." }`. Markdown or plain text is stored as-is and indexing is attempted before the response returns; check `indexStatus` (`status` is `completed`). Best for content you generate yourself, such as an SOP compiled from resolved conversations.
- **Remote file** — `{ "url": "https://..." }` (optional `name`). The file (PDF, DOCX, PPTX, XLSX, TXT, HTML, …; max 50 MB) is downloaded and converted to text in the background. The response returns immediately with `status: "processing"`; poll the file until it is `completed` or `failed`.
- **Multipart upload** — send `multipart/form-data` with the document in a `file` part and, optionally, a JSON `data` field such as `{"name": "..."}`. Converted in the background like a remote file.

**Keeping files in sync (create-or-update).** Pass your own `externalId` (unique per workspace) and the same request becomes idempotent: a new ID creates the file (`201`), a known ID with different content updates that file in place and re-indexes it (`200`), and a known ID with identical content is a no-op (`200`, no embedding work). Add a `source` label to group everything one pipeline owns, then list by `source` to find entries to remove. Every response carries `contentHash` so you can diff a whole library from the list endpoint.

Without `externalId`, uploading a file whose name and bytes match an existing file returns the existing file with status `200` instead of creating a duplicate. Multipart uploads over 50 MB are rejected with `413`.

### Parameters

- `params: FileCreateParams`

  - `content?: string`

    Body param: Text or markdown to train on. Stored as-is; indexing is attempted before returning. Check indexStatus. Cannot be combined with `url`.

  - `externalId?: string`

    Body param: Your own identifier for this entry (unique per workspace). When provided, the request creates the entry if the ID is new and otherwise updates the existing entry in place — an unchanged payload is a no-op. Use it to keep the AI agent in sync with a system of record without tracking Featurebase IDs.

  - `name?: string`

    Body param: Display name of the file. Required with `content`; optional with `url` (defaults to the downloaded file name) or a multipart upload (defaults to the uploaded file name).

  - `source?: string`

    Body param: Free-form label for the pipeline or system this entry came from. Filter lists by it to review or sweep everything from one source.

  - `url?: string`

    Body param: Public http(s) URL of a document to download and convert (PDF, DOCX, TXT, HTML, …; max 50 MB). Conversion runs in the background — poll the file until `status` is `completed`. Cannot be combined with `content`.

  - `featurebaseVersion?: "2026-08-19.orbit" | "2026-01-01.nova" | "2025-12-12.clover"`

    Header param: API version for this request. Defaults to your organization's configured API version if not specified.

    - `"2026-08-19.orbit"`

    - `"2026-01-01.nova"`

    - `"2025-12-12.clover"`

### Returns

- `TrainingFile`

  - `id: string`

    Training file ID

  - `characterCount: number | null`

    Characters of extracted text

  - `contentHash: string | null`

    SHA-256 (hex) of the stored source: for inline text and content edits the trimmed text, for uploads and URLs the original bytes. Compare it with your own hash to detect changes without downloading `content`.

  - `contentPreview: string | null`

    First 200 characters of the extracted text

  - `createdAt: string`

    ISO timestamp of the upload

  - `externalId: string | null`

    Your own identifier for this file, if one was set

  - `fileSize: number | null`

    Size of the originally uploaded source in bytes (unchanged by content edits)

  - `indexStatus: "pending" | "indexed" | "failed"`

    Whether the content is searchable by the AI agent. `pending` while indexing, `indexed` when the AI agent can use it, `failed` if indexing failed (the content is stored and indexing is retried hourly).

    - `"pending"`

    - `"indexed"`

    - `"failed"`

  - `mimeType: string | null`

    MIME type of the originally uploaded source

  - `name: string`

    File name shown in the dashboard

  - `object: "training_file"`

    Object type identifier

    - `"training_file"`

  - `processingError: string | null`

    Conversion error message when `status` is `failed`

  - `source: string | null`

    Pipeline label, if one was set

  - `status: "processing" | "completed" | "failed"`

    `processing` while the file is being converted to text, `completed` once the content is stored, `failed` if conversion failed (see `processingError`).

    - `"processing"`

    - `"completed"`

    - `"failed"`

  - `updatedAt: string`

    ISO timestamp of the last change

  - `wordCount: number | null`

    Words of extracted text

  - `content?: string | null`

    Full extracted text (markdown). Returned when retrieving or updating a single file; omitted from list and create responses.

  - `outcome?: "created" | "updated" | "unchanged"`

    On create and update responses: `created`, `updated` (same `externalId` or same name and bytes, content replaced and re-indexed), or `unchanged` (identical content, nothing done). Absent on reads.

    - `"created"`

    - `"updated"`

    - `"unchanged"`

### Example

```typescript
import Featurebase from 'featurebase-node';

const client = new Featurebase({
  apiKey: process.env['FEATUREBASE_API_KEY'], // This is the default and can be omitted
});

const trainingFile = await client.trainingData.files.create();

console.log(trainingFile.id);
```

#### Response

```json
{
  "id": "67ec1234abcd5678ef901234",
  "characterCount": 12840,
  "contentHash": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
  "contentPreview": "# Refund policy\n\nCustomers can request a refund within 30 days…",
  "createdAt": "2026-09-03T10:15:00.000Z",
  "externalId": "kb-article-1842",
  "fileSize": 48213,
  "indexStatus": "indexed",
  "mimeType": "application/pdf",
  "name": "refund-policy.pdf",
  "object": "training_file",
  "processingError": null,
  "source": "resolved-conversations",
  "status": "completed",
  "updatedAt": "2026-09-03T10:15:30.000Z",
  "wordCount": 2130,
  "content": "# Refund policy\n\nCustomers can request a refund within 30 days of purchase…",
  "outcome": "created"
}
```

## Get a training file

`client.trainingData.files.retrieve(stringid, FileRetrieveParamsparams?, RequestOptionsoptions?): TrainingFile`

**get** `/v2/training_data/files/{id}`

Returns a training file including its extracted text (`content`). Poll this endpoint after an upload until `status` is `completed`.

### Parameters

- `id: string`

  The training file ID

- `params: FileRetrieveParams`

  - `featurebaseVersion?: "2026-08-19.orbit" | "2026-01-01.nova" | "2025-12-12.clover"`

    API version for this request. Defaults to your organization's configured API version if not specified.

    - `"2026-08-19.orbit"`

    - `"2026-01-01.nova"`

    - `"2025-12-12.clover"`

### Returns

- `TrainingFile`

  - `id: string`

    Training file ID

  - `characterCount: number | null`

    Characters of extracted text

  - `contentHash: string | null`

    SHA-256 (hex) of the stored source: for inline text and content edits the trimmed text, for uploads and URLs the original bytes. Compare it with your own hash to detect changes without downloading `content`.

  - `contentPreview: string | null`

    First 200 characters of the extracted text

  - `createdAt: string`

    ISO timestamp of the upload

  - `externalId: string | null`

    Your own identifier for this file, if one was set

  - `fileSize: number | null`

    Size of the originally uploaded source in bytes (unchanged by content edits)

  - `indexStatus: "pending" | "indexed" | "failed"`

    Whether the content is searchable by the AI agent. `pending` while indexing, `indexed` when the AI agent can use it, `failed` if indexing failed (the content is stored and indexing is retried hourly).

    - `"pending"`

    - `"indexed"`

    - `"failed"`

  - `mimeType: string | null`

    MIME type of the originally uploaded source

  - `name: string`

    File name shown in the dashboard

  - `object: "training_file"`

    Object type identifier

    - `"training_file"`

  - `processingError: string | null`

    Conversion error message when `status` is `failed`

  - `source: string | null`

    Pipeline label, if one was set

  - `status: "processing" | "completed" | "failed"`

    `processing` while the file is being converted to text, `completed` once the content is stored, `failed` if conversion failed (see `processingError`).

    - `"processing"`

    - `"completed"`

    - `"failed"`

  - `updatedAt: string`

    ISO timestamp of the last change

  - `wordCount: number | null`

    Words of extracted text

  - `content?: string | null`

    Full extracted text (markdown). Returned when retrieving or updating a single file; omitted from list and create responses.

  - `outcome?: "created" | "updated" | "unchanged"`

    On create and update responses: `created`, `updated` (same `externalId` or same name and bytes, content replaced and re-indexed), or `unchanged` (identical content, nothing done). Absent on reads.

    - `"created"`

    - `"updated"`

    - `"unchanged"`

### Example

```typescript
import Featurebase from 'featurebase-node';

const client = new Featurebase({
  apiKey: process.env['FEATUREBASE_API_KEY'], // This is the default and can be omitted
});

const trainingFile = await client.trainingData.files.retrieve('67ec1234abcd5678ef901234');

console.log(trainingFile.id);
```

#### Response

```json
{
  "id": "67ec1234abcd5678ef901234",
  "characterCount": 12840,
  "contentHash": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
  "contentPreview": "# Refund policy\n\nCustomers can request a refund within 30 days…",
  "createdAt": "2026-09-03T10:15:00.000Z",
  "externalId": "kb-article-1842",
  "fileSize": 48213,
  "indexStatus": "indexed",
  "mimeType": "application/pdf",
  "name": "refund-policy.pdf",
  "object": "training_file",
  "processingError": null,
  "source": "resolved-conversations",
  "status": "completed",
  "updatedAt": "2026-09-03T10:15:30.000Z",
  "wordCount": 2130,
  "content": "# Refund policy\n\nCustomers can request a refund within 30 days of purchase…",
  "outcome": "created"
}
```

## Update a training file

`client.trainingData.files.update(stringid, FileUpdateParamsparams, RequestOptionsoptions?): TrainingFile`

**patch** `/v2/training_data/files/{id}`

Renames a training file, replaces content, or sets externalId / source. Indexing is attempted before returning when content is available. Check status and indexStatus; HTTP success alone does not establish search readiness. Serialize updates to a source identity in your pipeline.

### Parameters

- `id: string`

  The training file ID

- `params: FileUpdateParams`

  - `content?: string`

    Body param: Replacement text/markdown. Indexing is attempted before returning; check indexStatus.

  - `externalId?: string`

    Body param: Your own identifier for this entry (unique per workspace).

  - `name?: string`

    Body param: New display name

  - `source?: string`

    Body param: Free-form label for the pipeline or system this entry came from. Filter lists by it to review or sweep everything from one source.

  - `featurebaseVersion?: "2026-08-19.orbit" | "2026-01-01.nova" | "2025-12-12.clover"`

    Header param: API version for this request. Defaults to your organization's configured API version if not specified.

    - `"2026-08-19.orbit"`

    - `"2026-01-01.nova"`

    - `"2025-12-12.clover"`

### Returns

- `TrainingFile`

  - `id: string`

    Training file ID

  - `characterCount: number | null`

    Characters of extracted text

  - `contentHash: string | null`

    SHA-256 (hex) of the stored source: for inline text and content edits the trimmed text, for uploads and URLs the original bytes. Compare it with your own hash to detect changes without downloading `content`.

  - `contentPreview: string | null`

    First 200 characters of the extracted text

  - `createdAt: string`

    ISO timestamp of the upload

  - `externalId: string | null`

    Your own identifier for this file, if one was set

  - `fileSize: number | null`

    Size of the originally uploaded source in bytes (unchanged by content edits)

  - `indexStatus: "pending" | "indexed" | "failed"`

    Whether the content is searchable by the AI agent. `pending` while indexing, `indexed` when the AI agent can use it, `failed` if indexing failed (the content is stored and indexing is retried hourly).

    - `"pending"`

    - `"indexed"`

    - `"failed"`

  - `mimeType: string | null`

    MIME type of the originally uploaded source

  - `name: string`

    File name shown in the dashboard

  - `object: "training_file"`

    Object type identifier

    - `"training_file"`

  - `processingError: string | null`

    Conversion error message when `status` is `failed`

  - `source: string | null`

    Pipeline label, if one was set

  - `status: "processing" | "completed" | "failed"`

    `processing` while the file is being converted to text, `completed` once the content is stored, `failed` if conversion failed (see `processingError`).

    - `"processing"`

    - `"completed"`

    - `"failed"`

  - `updatedAt: string`

    ISO timestamp of the last change

  - `wordCount: number | null`

    Words of extracted text

  - `content?: string | null`

    Full extracted text (markdown). Returned when retrieving or updating a single file; omitted from list and create responses.

  - `outcome?: "created" | "updated" | "unchanged"`

    On create and update responses: `created`, `updated` (same `externalId` or same name and bytes, content replaced and re-indexed), or `unchanged` (identical content, nothing done). Absent on reads.

    - `"created"`

    - `"updated"`

    - `"unchanged"`

### Example

```typescript
import Featurebase from 'featurebase-node';

const client = new Featurebase({
  apiKey: process.env['FEATUREBASE_API_KEY'], // This is the default and can be omitted
});

const trainingFile = await client.trainingData.files.update('67ec1234abcd5678ef901234');

console.log(trainingFile.id);
```

#### Response

```json
{
  "id": "67ec1234abcd5678ef901234",
  "characterCount": 12840,
  "contentHash": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
  "contentPreview": "# Refund policy\n\nCustomers can request a refund within 30 days…",
  "createdAt": "2026-09-03T10:15:00.000Z",
  "externalId": "kb-article-1842",
  "fileSize": 48213,
  "indexStatus": "indexed",
  "mimeType": "application/pdf",
  "name": "refund-policy.pdf",
  "object": "training_file",
  "processingError": null,
  "source": "resolved-conversations",
  "status": "completed",
  "updatedAt": "2026-09-03T10:15:30.000Z",
  "wordCount": 2130,
  "content": "# Refund policy\n\nCustomers can request a refund within 30 days of purchase…",
  "outcome": "created"
}
```

## Delete a training file

`client.trainingData.files.delete(stringid, FileDeleteParamsparams?, RequestOptionsoptions?): FileDeleteResponse`

**delete** `/v2/training_data/files/{id}`

Removes a training file from the workspace and attempts search-index cleanup. In-flight answers may already contain earlier knowledge.

### Parameters

- `id: string`

  The training file ID

- `params: FileDeleteParams`

  - `featurebaseVersion?: "2026-08-19.orbit" | "2026-01-01.nova" | "2025-12-12.clover"`

    API version for this request. Defaults to your organization's configured API version if not specified.

    - `"2026-08-19.orbit"`

    - `"2026-01-01.nova"`

    - `"2025-12-12.clover"`

### Returns

- `FileDeleteResponse`

  - `id: string`

    ID of the deleted training file

  - `deleted: true`

    Indicates the resource was deleted

    - `true`

  - `object: "training_file"`

    Object type identifier

    - `"training_file"`

### Example

```typescript
import Featurebase from 'featurebase-node';

const client = new Featurebase({
  apiKey: process.env['FEATUREBASE_API_KEY'], // This is the default and can be omitted
});

const file = await client.trainingData.files.delete('67ec1234abcd5678ef901234');

console.log(file.id);
```

#### Response

```json
{
  "id": "67ec1234abcd5678ef901234",
  "deleted": true,
  "object": "training_file"
}
```

## Domain Types

### Training File

- `TrainingFile`

  - `id: string`

    Training file ID

  - `characterCount: number | null`

    Characters of extracted text

  - `contentHash: string | null`

    SHA-256 (hex) of the stored source: for inline text and content edits the trimmed text, for uploads and URLs the original bytes. Compare it with your own hash to detect changes without downloading `content`.

  - `contentPreview: string | null`

    First 200 characters of the extracted text

  - `createdAt: string`

    ISO timestamp of the upload

  - `externalId: string | null`

    Your own identifier for this file, if one was set

  - `fileSize: number | null`

    Size of the originally uploaded source in bytes (unchanged by content edits)

  - `indexStatus: "pending" | "indexed" | "failed"`

    Whether the content is searchable by the AI agent. `pending` while indexing, `indexed` when the AI agent can use it, `failed` if indexing failed (the content is stored and indexing is retried hourly).

    - `"pending"`

    - `"indexed"`

    - `"failed"`

  - `mimeType: string | null`

    MIME type of the originally uploaded source

  - `name: string`

    File name shown in the dashboard

  - `object: "training_file"`

    Object type identifier

    - `"training_file"`

  - `processingError: string | null`

    Conversion error message when `status` is `failed`

  - `source: string | null`

    Pipeline label, if one was set

  - `status: "processing" | "completed" | "failed"`

    `processing` while the file is being converted to text, `completed` once the content is stored, `failed` if conversion failed (see `processingError`).

    - `"processing"`

    - `"completed"`

    - `"failed"`

  - `updatedAt: string`

    ISO timestamp of the last change

  - `wordCount: number | null`

    Words of extracted text

  - `content?: string | null`

    Full extracted text (markdown). Returned when retrieving or updating a single file; omitted from list and create responses.

  - `outcome?: "created" | "updated" | "unchanged"`

    On create and update responses: `created`, `updated` (same `externalId` or same name and bytes, content replaced and re-indexed), or `unchanged` (identical content, nothing done). Absent on reads.

    - `"created"`

    - `"updated"`

    - `"unchanged"`

### File Delete Response

- `FileDeleteResponse`

  - `id: string`

    ID of the deleted training file

  - `deleted: true`

    Indicates the resource was deleted

    - `true`

  - `object: "training_file"`

    Object type identifier

    - `"training_file"`

# Qna

## List Q&A entries

`client.trainingData.qna.list(QnaListParamsparams?, RequestOptionsoptions?): CursorPage<Qna>`

**get** `/v2/training_data/qna`

Returns Q&A entries, newest first, with cursor pagination. Filter by `externalId` or `source`. Entries include their full questions and answer.

### Parameters

- `params: QnaListParams`

  - `cursor?: string`

    Query param: An opaque cursor for pagination. Use the nextCursor value from a previous response to fetch the next page of results.

  - `externalId?: string`

    Query param: Only return the entry with this `externalId`

  - `limit?: number`

    Query param: A limit on the number of objects to be returned, between 1 and 100.

  - `source?: string`

    Query param: Only return entries with this `source` label

  - `featurebaseVersion?: "2026-08-19.orbit" | "2026-01-01.nova" | "2025-12-12.clover"`

    Header param: API version for this request. Defaults to your organization's configured API version if not specified.

    - `"2026-08-19.orbit"`

    - `"2026-01-01.nova"`

    - `"2025-12-12.clover"`

### Returns

- `Qna`

  - `id: string`

    Q&A entry ID

  - `answer: string`

    The answer the AI agent gives (markdown)

  - `createdAt: string`

    ISO timestamp of creation

  - `externalId: string | null`

    Stable identifier supplied by the source system

  - `indexStatus: "pending" | "indexed" | "failed"`

    Whether the entry is searchable by the AI agent. `failed` entries are stored and indexing is retried hourly up to three times. Re-send the resource to retry after exhaustion.

    - `"pending"`

    - `"indexed"`

    - `"failed"`

  - `object: "qna"`

    Object type identifier

    - `"qna"`

  - `questions: Array<string>`

    Question variants the answer applies to

  - `revision: number`

    Q&A revision. Send it as expectedRevision on PATCH to reject stale changes.

  - `source: string | null`

    Pipeline label, if one was set

  - `title: string`

    Short label for the entry

  - `updatedAt: string`

    ISO timestamp of the last change

  - `match?: Match | null`

    Present only when `onMatch` was passed and the check ran: the verdict and the closest existing item, even when the write went ahead. `null` otherwise (the default), including when the payload was unchanged.

    - `entry: Entry | null`

      The existing item the verdict is about, or `null` when nothing was close

      - `id: string`

        ID of the item (Q&A entry, training file, or article)

      - `kind: "qna" | "training_file" | "help_center_article"`

        What kind of knowledge item matched: a Q&A entry, a training file / crawled page, or a live help-center article.

        - `"qna"`

        - `"training_file"`

        - `"help_center_article"`

      - `section: string | null`

        Heading breadcrumb of the matching section, for training files

      - `similarity: number | null`

        Cosine similarity between your entry and the item (0–1); `null` for a keyword-only hit

      - `title: string`

        Title of the item

      - `url: string | null`

        Public URL of the item when it has one (articles, crawled pages)

    - `reason: string | null`

      The judge's one-line explanation of the verdict

    - `verdict: "same_topic" | "contradicts" | "different" | 2 more`

      How the closest existing item relates to the entry. `same_topic`: it answers the same customer question (the entry would be a duplicate, rewording, or updated version). `contradicts`: same question, incompatible facts. `different`: a different question, even if the wording overlaps. `unclear`: the judge could not decide. `no_match`: nothing similar was found.

      - `"same_topic"`

      - `"contradicts"`

      - `"different"`

      - `"unclear"`

      - `"no_match"`

  - `outcome?: "created" | "updated" | "unchanged"`

    What the write did — present on create and update responses. `created`: a new entry. `updated`: an entry already carried one of the questions (or was merged into with `onMatch: "update"`) and got new content or an indexing retry. `unchanged`: everything in the payload was already on that entry; nothing written, nothing re-indexed.

    - `"created"`

    - `"updated"`

    - `"unchanged"`

### Example

```typescript
import Featurebase from 'featurebase-node';

const client = new Featurebase({
  apiKey: process.env['FEATUREBASE_API_KEY'], // This is the default and can be omitted
});

// Automatically fetches more pages as needed.
for await (const qna of client.trainingData.qna.list()) {
  console.log(qna.id);
}
```

#### Response

```json
{
  "data": [
    {
      "id": "67ec1234abcd5678ef901235",
      "answer": "You can request a refund within 30 days from **Settings → Billing**.",
      "createdAt": "2026-09-03T10:15:00.000Z",
      "externalId": "resolution-1842",
      "indexStatus": "indexed",
      "object": "qna",
      "questions": [
        "How do I request a refund?",
        "Can I get my money back?"
      ],
      "revision": 1,
      "source": "resolved-conversations",
      "title": "Refund requests",
      "updatedAt": "2026-09-03T10:15:30.000Z",
      "match": {
        "entry": {
          "id": "67ec1234abcd5678ef901236",
          "kind": "qna",
          "section": null,
          "similarity": 0.61,
          "title": "Refund requests",
          "url": null
        },
        "reason": "Both answer how to request a refund; the new entry adds the 30-day window.",
        "verdict": "same_topic"
      },
      "outcome": "created"
    }
  ],
  "nextCursor": "eyJpZCI6IjUwN2YxZjc3YmNmODZjZDc5OTQzOTAxMSJ9",
  "object": "list"
}
```

## Create a Q&A entry

`client.trainingData.qna.create(QnaCreateParamsparams, RequestOptionsoptions?): Qna`

**post** `/v2/training_data/qna`

Creates or updates question variants and an answer. Optional externalId is a stable, caller-owned key, unique within workspace Q&A. Otherwise a normalized question addresses an existing entry. POST appends variants (maximum 50) and replaces supplied content; PATCH replaces the question list. Questions cannot belong to multiple entries: ambiguous ownership returns 409 question_already_used. New entries return 201; updates and no-ops return 200. Responses include externalId, revision, and indexStatus. Indexing is attempted before returning; verify indexStatus rather than assuming HTTP success means searchable.

Writes omit semantic checks by default. Optional onMatch runs a bounded check against the addressed entry and retrieved knowledge: reject refuses overlap; update explicitly permits answer replacement, including contradictions, but refuses unclear results; create explicitly overrides the check. Without an addressed entry, update can merge a matching Q&A, subject to source ownership. Articles and files are never edited. Check failures return 503 before writing. This is not a transaction over the knowledge base: other entries can change concurrently. After a separate oracle call, use PATCH with the checked expectedRevision; stale writes return 409 revision_conflict.

### Parameters

- `params: QnaCreateParams`

  - `answer: string`

    Body param: The answer the AI agent should give

  - `questions: Array<string>`

    Body param: Question variants this answer applies to (1–50). Phrase them the way customers ask.

  - `answerFormat?: "markdown" | "html"`

    Body param: Format of `answer`. `markdown` (default) is stored as-is; `html` is converted to markdown.

    - `"markdown"`

    - `"html"`

  - `externalId?: string`

    Body param: Your own identifier for this entry (unique per workspace). When provided, the request creates the entry if the ID is new and otherwise updates the existing entry in place — an unchanged payload is a no-op. Use it to keep the AI agent in sync with a system of record without tracking Featurebase IDs.

  - `onMatch?: "reject" | "update" | "create"`

    Body param: Optional inline knowledge check. Omit it (the default) and the entry is written immediately without consulting existing knowledge — ask `POST /v2/training_data/oracle` first when you want a suggestion. Pass it to run the same check as the oracle inside this request and act on the result (this is not a transaction over the knowledge base): `reject`: respond `409 conflicts_with_existing` describing the match and write nothing. `update`: when an entry already carries one of your questions, write to that entry and report the match; otherwise, if the match is another Q&A entry, merge into it (question variants appended, answer replaced); an article or file is never edited — the entry is written as requested and the match reported. `create`: write as requested and report the match. `unclear` is treated as `reject` for `update`. With `onMatch` set, the request fails with `503 knowledge_check_unavailable` instead of writing when the check cannot run.

    - `"reject"`

    - `"update"`

    - `"create"`

  - `source?: string`

    Body param: Free-form label for the pipeline or system this entry came from. Filter lists by it to review or sweep everything from one source.

  - `title?: string`

    Body param: Short label for the entry. Optional: a new entry defaults to its first question; an existing entry keeps its title when omitted.

  - `featurebaseVersion?: "2026-08-19.orbit" | "2026-01-01.nova" | "2025-12-12.clover"`

    Header param: API version for this request. Defaults to your organization's configured API version if not specified.

    - `"2026-08-19.orbit"`

    - `"2026-01-01.nova"`

    - `"2025-12-12.clover"`

### Returns

- `Qna`

  - `id: string`

    Q&A entry ID

  - `answer: string`

    The answer the AI agent gives (markdown)

  - `createdAt: string`

    ISO timestamp of creation

  - `externalId: string | null`

    Stable identifier supplied by the source system

  - `indexStatus: "pending" | "indexed" | "failed"`

    Whether the entry is searchable by the AI agent. `failed` entries are stored and indexing is retried hourly up to three times. Re-send the resource to retry after exhaustion.

    - `"pending"`

    - `"indexed"`

    - `"failed"`

  - `object: "qna"`

    Object type identifier

    - `"qna"`

  - `questions: Array<string>`

    Question variants the answer applies to

  - `revision: number`

    Q&A revision. Send it as expectedRevision on PATCH to reject stale changes.

  - `source: string | null`

    Pipeline label, if one was set

  - `title: string`

    Short label for the entry

  - `updatedAt: string`

    ISO timestamp of the last change

  - `match?: Match | null`

    Present only when `onMatch` was passed and the check ran: the verdict and the closest existing item, even when the write went ahead. `null` otherwise (the default), including when the payload was unchanged.

    - `entry: Entry | null`

      The existing item the verdict is about, or `null` when nothing was close

      - `id: string`

        ID of the item (Q&A entry, training file, or article)

      - `kind: "qna" | "training_file" | "help_center_article"`

        What kind of knowledge item matched: a Q&A entry, a training file / crawled page, or a live help-center article.

        - `"qna"`

        - `"training_file"`

        - `"help_center_article"`

      - `section: string | null`

        Heading breadcrumb of the matching section, for training files

      - `similarity: number | null`

        Cosine similarity between your entry and the item (0–1); `null` for a keyword-only hit

      - `title: string`

        Title of the item

      - `url: string | null`

        Public URL of the item when it has one (articles, crawled pages)

    - `reason: string | null`

      The judge's one-line explanation of the verdict

    - `verdict: "same_topic" | "contradicts" | "different" | 2 more`

      How the closest existing item relates to the entry. `same_topic`: it answers the same customer question (the entry would be a duplicate, rewording, or updated version). `contradicts`: same question, incompatible facts. `different`: a different question, even if the wording overlaps. `unclear`: the judge could not decide. `no_match`: nothing similar was found.

      - `"same_topic"`

      - `"contradicts"`

      - `"different"`

      - `"unclear"`

      - `"no_match"`

  - `outcome?: "created" | "updated" | "unchanged"`

    What the write did — present on create and update responses. `created`: a new entry. `updated`: an entry already carried one of the questions (or was merged into with `onMatch: "update"`) and got new content or an indexing retry. `unchanged`: everything in the payload was already on that entry; nothing written, nothing re-indexed.

    - `"created"`

    - `"updated"`

    - `"unchanged"`

### Example

```typescript
import Featurebase from 'featurebase-node';

const client = new Featurebase({
  apiKey: process.env['FEATUREBASE_API_KEY'], // This is the default and can be omitted
});

const qna = await client.trainingData.qna.create({
  answer: 'You can request a refund within 30 days from **Settings → Billing**.',
  questions: ['How do I request a refund?', 'Can I get my money back?'],
});

console.log(qna.id);
```

#### Response

```json
{
  "id": "67ec1234abcd5678ef901235",
  "answer": "You can request a refund within 30 days from **Settings → Billing**.",
  "createdAt": "2026-09-03T10:15:00.000Z",
  "externalId": "resolution-1842",
  "indexStatus": "indexed",
  "object": "qna",
  "questions": [
    "How do I request a refund?",
    "Can I get my money back?"
  ],
  "revision": 1,
  "source": "resolved-conversations",
  "title": "Refund requests",
  "updatedAt": "2026-09-03T10:15:30.000Z",
  "match": {
    "entry": {
      "id": "67ec1234abcd5678ef901236",
      "kind": "qna",
      "section": null,
      "similarity": 0.61,
      "title": "Refund requests",
      "url": null
    },
    "reason": "Both answer how to request a refund; the new entry adds the 30-day window.",
    "verdict": "same_topic"
  },
  "outcome": "created"
}
```

## Get a Q&A entry

`client.trainingData.qna.retrieve(stringid, QnaRetrieveParamsparams?, RequestOptionsoptions?): Qna`

**get** `/v2/training_data/qna/{id}`

Returns a single Q&A entry by ID.

### Parameters

- `id: string`

  The Q&A entry ID

- `params: QnaRetrieveParams`

  - `featurebaseVersion?: "2026-08-19.orbit" | "2026-01-01.nova" | "2025-12-12.clover"`

    API version for this request. Defaults to your organization's configured API version if not specified.

    - `"2026-08-19.orbit"`

    - `"2026-01-01.nova"`

    - `"2025-12-12.clover"`

### Returns

- `Qna`

  - `id: string`

    Q&A entry ID

  - `answer: string`

    The answer the AI agent gives (markdown)

  - `createdAt: string`

    ISO timestamp of creation

  - `externalId: string | null`

    Stable identifier supplied by the source system

  - `indexStatus: "pending" | "indexed" | "failed"`

    Whether the entry is searchable by the AI agent. `failed` entries are stored and indexing is retried hourly up to three times. Re-send the resource to retry after exhaustion.

    - `"pending"`

    - `"indexed"`

    - `"failed"`

  - `object: "qna"`

    Object type identifier

    - `"qna"`

  - `questions: Array<string>`

    Question variants the answer applies to

  - `revision: number`

    Q&A revision. Send it as expectedRevision on PATCH to reject stale changes.

  - `source: string | null`

    Pipeline label, if one was set

  - `title: string`

    Short label for the entry

  - `updatedAt: string`

    ISO timestamp of the last change

  - `match?: Match | null`

    Present only when `onMatch` was passed and the check ran: the verdict and the closest existing item, even when the write went ahead. `null` otherwise (the default), including when the payload was unchanged.

    - `entry: Entry | null`

      The existing item the verdict is about, or `null` when nothing was close

      - `id: string`

        ID of the item (Q&A entry, training file, or article)

      - `kind: "qna" | "training_file" | "help_center_article"`

        What kind of knowledge item matched: a Q&A entry, a training file / crawled page, or a live help-center article.

        - `"qna"`

        - `"training_file"`

        - `"help_center_article"`

      - `section: string | null`

        Heading breadcrumb of the matching section, for training files

      - `similarity: number | null`

        Cosine similarity between your entry and the item (0–1); `null` for a keyword-only hit

      - `title: string`

        Title of the item

      - `url: string | null`

        Public URL of the item when it has one (articles, crawled pages)

    - `reason: string | null`

      The judge's one-line explanation of the verdict

    - `verdict: "same_topic" | "contradicts" | "different" | 2 more`

      How the closest existing item relates to the entry. `same_topic`: it answers the same customer question (the entry would be a duplicate, rewording, or updated version). `contradicts`: same question, incompatible facts. `different`: a different question, even if the wording overlaps. `unclear`: the judge could not decide. `no_match`: nothing similar was found.

      - `"same_topic"`

      - `"contradicts"`

      - `"different"`

      - `"unclear"`

      - `"no_match"`

  - `outcome?: "created" | "updated" | "unchanged"`

    What the write did — present on create and update responses. `created`: a new entry. `updated`: an entry already carried one of the questions (or was merged into with `onMatch: "update"`) and got new content or an indexing retry. `unchanged`: everything in the payload was already on that entry; nothing written, nothing re-indexed.

    - `"created"`

    - `"updated"`

    - `"unchanged"`

### Example

```typescript
import Featurebase from 'featurebase-node';

const client = new Featurebase({
  apiKey: process.env['FEATUREBASE_API_KEY'], // This is the default and can be omitted
});

const qna = await client.trainingData.qna.retrieve('67ec1234abcd5678ef901235');

console.log(qna.id);
```

#### Response

```json
{
  "id": "67ec1234abcd5678ef901235",
  "answer": "You can request a refund within 30 days from **Settings → Billing**.",
  "createdAt": "2026-09-03T10:15:00.000Z",
  "externalId": "resolution-1842",
  "indexStatus": "indexed",
  "object": "qna",
  "questions": [
    "How do I request a refund?",
    "Can I get my money back?"
  ],
  "revision": 1,
  "source": "resolved-conversations",
  "title": "Refund requests",
  "updatedAt": "2026-09-03T10:15:30.000Z",
  "match": {
    "entry": {
      "id": "67ec1234abcd5678ef901236",
      "kind": "qna",
      "section": null,
      "similarity": 0.61,
      "title": "Refund requests",
      "url": null
    },
    "reason": "Both answer how to request a refund; the new entry adds the 30-day window.",
    "verdict": "same_topic"
  },
  "outcome": "created"
}
```

## Update a Q&A entry

`client.trainingData.qna.update(stringid, QnaUpdateParamsparams, RequestOptionsoptions?): Qna`

**patch** `/v2/training_data/qna/{id}`

Updates title, questions, answer, or source. Questions replaces the whole list; preserve the union yourself when merging. Supply expectedRevision from GET or oracle.existing / oracle.target to apply only to the version you reviewed. A stale revision returns 409 revision_conflict; a question owned by another entry returns 409 question_already_used. Content changes attempt indexing before returning; check indexStatus.

Optional onMatch checks changed questions or answer against the addressed entry and retrieved candidates. reject refuses overlap; update permits deliberate replacement but refuses unclear results; create overrides the result. PATCH never merges entries. Checks are bounded advice and do not lock the rest of the knowledge base. A missing onMatch applies the requested update without semantic judging.

### Parameters

- `id: string`

  The Q&A entry ID

- `params: QnaUpdateParams`

  - `answer?: string`

    Body param: Replacement answer

  - `answerFormat?: "markdown" | "html"`

    Body param: Format of `answer`. `markdown` (default) is stored as-is; `html` is converted to markdown.

    - `"markdown"`

    - `"html"`

  - `expectedRevision?: number`

    Body param: Apply only if the entry still has this revision. A stale revision returns 409 revision_conflict without writing. Get the revision from retrieve, oracle.existing, or oracle.target.

  - `onMatch?: "reject" | "update" | "create"`

    Body param: Optional inline knowledge check for a content change. Omit it (the default) and the change is applied immediately. Pass it to check the changed `questions` / `answer` against the addressed entry and retrieved workspace candidates first: `reject`: respond `409 conflicts_with_existing` and leave the entry untouched; `update` or `create`: apply the change and report the match in `match`. A PATCH never merges two entries. Ignored when neither `questions` nor `answer` changes.

    - `"reject"`

    - `"update"`

    - `"create"`

  - `questions?: Array<string>`

    Body param: Replacement question list (replaces all existing questions)

  - `source?: string`

    Body param: Free-form label for the pipeline or system this entry came from. Filter lists by it to review or sweep everything from one source.

  - `title?: string`

    Body param: New label

  - `featurebaseVersion?: "2026-08-19.orbit" | "2026-01-01.nova" | "2025-12-12.clover"`

    Header param: API version for this request. Defaults to your organization's configured API version if not specified.

    - `"2026-08-19.orbit"`

    - `"2026-01-01.nova"`

    - `"2025-12-12.clover"`

### Returns

- `Qna`

  - `id: string`

    Q&A entry ID

  - `answer: string`

    The answer the AI agent gives (markdown)

  - `createdAt: string`

    ISO timestamp of creation

  - `externalId: string | null`

    Stable identifier supplied by the source system

  - `indexStatus: "pending" | "indexed" | "failed"`

    Whether the entry is searchable by the AI agent. `failed` entries are stored and indexing is retried hourly up to three times. Re-send the resource to retry after exhaustion.

    - `"pending"`

    - `"indexed"`

    - `"failed"`

  - `object: "qna"`

    Object type identifier

    - `"qna"`

  - `questions: Array<string>`

    Question variants the answer applies to

  - `revision: number`

    Q&A revision. Send it as expectedRevision on PATCH to reject stale changes.

  - `source: string | null`

    Pipeline label, if one was set

  - `title: string`

    Short label for the entry

  - `updatedAt: string`

    ISO timestamp of the last change

  - `match?: Match | null`

    Present only when `onMatch` was passed and the check ran: the verdict and the closest existing item, even when the write went ahead. `null` otherwise (the default), including when the payload was unchanged.

    - `entry: Entry | null`

      The existing item the verdict is about, or `null` when nothing was close

      - `id: string`

        ID of the item (Q&A entry, training file, or article)

      - `kind: "qna" | "training_file" | "help_center_article"`

        What kind of knowledge item matched: a Q&A entry, a training file / crawled page, or a live help-center article.

        - `"qna"`

        - `"training_file"`

        - `"help_center_article"`

      - `section: string | null`

        Heading breadcrumb of the matching section, for training files

      - `similarity: number | null`

        Cosine similarity between your entry and the item (0–1); `null` for a keyword-only hit

      - `title: string`

        Title of the item

      - `url: string | null`

        Public URL of the item when it has one (articles, crawled pages)

    - `reason: string | null`

      The judge's one-line explanation of the verdict

    - `verdict: "same_topic" | "contradicts" | "different" | 2 more`

      How the closest existing item relates to the entry. `same_topic`: it answers the same customer question (the entry would be a duplicate, rewording, or updated version). `contradicts`: same question, incompatible facts. `different`: a different question, even if the wording overlaps. `unclear`: the judge could not decide. `no_match`: nothing similar was found.

      - `"same_topic"`

      - `"contradicts"`

      - `"different"`

      - `"unclear"`

      - `"no_match"`

  - `outcome?: "created" | "updated" | "unchanged"`

    What the write did — present on create and update responses. `created`: a new entry. `updated`: an entry already carried one of the questions (or was merged into with `onMatch: "update"`) and got new content or an indexing retry. `unchanged`: everything in the payload was already on that entry; nothing written, nothing re-indexed.

    - `"created"`

    - `"updated"`

    - `"unchanged"`

### Example

```typescript
import Featurebase from 'featurebase-node';

const client = new Featurebase({
  apiKey: process.env['FEATUREBASE_API_KEY'], // This is the default and can be omitted
});

const qna = await client.trainingData.qna.update('67ec1234abcd5678ef901235');

console.log(qna.id);
```

#### Response

```json
{
  "id": "67ec1234abcd5678ef901235",
  "answer": "You can request a refund within 30 days from **Settings → Billing**.",
  "createdAt": "2026-09-03T10:15:00.000Z",
  "externalId": "resolution-1842",
  "indexStatus": "indexed",
  "object": "qna",
  "questions": [
    "How do I request a refund?",
    "Can I get my money back?"
  ],
  "revision": 1,
  "source": "resolved-conversations",
  "title": "Refund requests",
  "updatedAt": "2026-09-03T10:15:30.000Z",
  "match": {
    "entry": {
      "id": "67ec1234abcd5678ef901236",
      "kind": "qna",
      "section": null,
      "similarity": 0.61,
      "title": "Refund requests",
      "url": null
    },
    "reason": "Both answer how to request a refund; the new entry adds the 30-day window.",
    "verdict": "same_topic"
  },
  "outcome": "created"
}
```

## Delete a Q&A entry

`client.trainingData.qna.delete(stringid, QnaDeleteParamsparams?, RequestOptionsoptions?): QnaDeleteResponse`

**delete** `/v2/training_data/qna/{id}`

Removes a Q&A entry from the workspace and attempts search-index cleanup. Delete only entries your pipeline owns; a merged entry may have several contributing sources.

### Parameters

- `id: string`

  The Q&A entry ID

- `params: QnaDeleteParams`

  - `featurebaseVersion?: "2026-08-19.orbit" | "2026-01-01.nova" | "2025-12-12.clover"`

    API version for this request. Defaults to your organization's configured API version if not specified.

    - `"2026-08-19.orbit"`

    - `"2026-01-01.nova"`

    - `"2025-12-12.clover"`

### Returns

- `QnaDeleteResponse`

  - `id: string`

    ID of the deleted Q&A entry

  - `deleted: true`

    Indicates the resource was deleted

    - `true`

  - `object: "qna"`

    Object type identifier

    - `"qna"`

### Example

```typescript
import Featurebase from 'featurebase-node';

const client = new Featurebase({
  apiKey: process.env['FEATUREBASE_API_KEY'], // This is the default and can be omitted
});

const qna = await client.trainingData.qna.delete('67ec1234abcd5678ef901235');

console.log(qna.id);
```

#### Response

```json
{
  "id": "67ec1234abcd5678ef901235",
  "deleted": true,
  "object": "qna"
}
```

## Domain Types

### Qna

- `Qna`

  - `id: string`

    Q&A entry ID

  - `answer: string`

    The answer the AI agent gives (markdown)

  - `createdAt: string`

    ISO timestamp of creation

  - `externalId: string | null`

    Stable identifier supplied by the source system

  - `indexStatus: "pending" | "indexed" | "failed"`

    Whether the entry is searchable by the AI agent. `failed` entries are stored and indexing is retried hourly up to three times. Re-send the resource to retry after exhaustion.

    - `"pending"`

    - `"indexed"`

    - `"failed"`

  - `object: "qna"`

    Object type identifier

    - `"qna"`

  - `questions: Array<string>`

    Question variants the answer applies to

  - `revision: number`

    Q&A revision. Send it as expectedRevision on PATCH to reject stale changes.

  - `source: string | null`

    Pipeline label, if one was set

  - `title: string`

    Short label for the entry

  - `updatedAt: string`

    ISO timestamp of the last change

  - `match?: Match | null`

    Present only when `onMatch` was passed and the check ran: the verdict and the closest existing item, even when the write went ahead. `null` otherwise (the default), including when the payload was unchanged.

    - `entry: Entry | null`

      The existing item the verdict is about, or `null` when nothing was close

      - `id: string`

        ID of the item (Q&A entry, training file, or article)

      - `kind: "qna" | "training_file" | "help_center_article"`

        What kind of knowledge item matched: a Q&A entry, a training file / crawled page, or a live help-center article.

        - `"qna"`

        - `"training_file"`

        - `"help_center_article"`

      - `section: string | null`

        Heading breadcrumb of the matching section, for training files

      - `similarity: number | null`

        Cosine similarity between your entry and the item (0–1); `null` for a keyword-only hit

      - `title: string`

        Title of the item

      - `url: string | null`

        Public URL of the item when it has one (articles, crawled pages)

    - `reason: string | null`

      The judge's one-line explanation of the verdict

    - `verdict: "same_topic" | "contradicts" | "different" | 2 more`

      How the closest existing item relates to the entry. `same_topic`: it answers the same customer question (the entry would be a duplicate, rewording, or updated version). `contradicts`: same question, incompatible facts. `different`: a different question, even if the wording overlaps. `unclear`: the judge could not decide. `no_match`: nothing similar was found.

      - `"same_topic"`

      - `"contradicts"`

      - `"different"`

      - `"unclear"`

      - `"no_match"`

  - `outcome?: "created" | "updated" | "unchanged"`

    What the write did — present on create and update responses. `created`: a new entry. `updated`: an entry already carried one of the questions (or was merged into with `onMatch: "update"`) and got new content or an indexing retry. `unchanged`: everything in the payload was already on that entry; nothing written, nothing re-indexed.

    - `"created"`

    - `"updated"`

    - `"unchanged"`

### Qna Delete Response

- `QnaDeleteResponse`

  - `id: string`

    ID of the deleted Q&A entry

  - `deleted: true`

    Indicates the resource was deleted

    - `true`

  - `object: "qna"`

    Object type identifier

    - `"qna"`
