---
title: Training Data API | Featurebase
description: Keep the AI agent's knowledge in sync with reviewed documents and Q&A entries from your own systems.
---

The Training Data API manages the knowledge available to your customer-support AI agent. Use it to publish reviewed policies, runbooks, and answers from your systems. It provides storage, retrieval, and a bounded knowledge check; your pipeline controls which proposed changes are approved and published.

Endpoints live under `/v2/training_data`, require API version `2026-01-01.nova` or newer, and accept your regular API key:

```
Authorization: Bearer sk_...
```

## Resources and identity

| Resource       | Path                      | Identity                                                                             |
| -------------- | ------------------------- | ------------------------------------------------------------------------------------ |
| Training files | `/v2/training_data/files` | Optional `externalId`, unique within workspace files                                 |
| Q\&A entries   | `/v2/training_data/qna`   | Optional `externalId`, unique within workspace Q\&A; otherwise a normalized question |

Both support list, create, retrieve, update, and delete. Lists use `limit` and `cursor`, returning `nextCursor`. Filter by `externalId` or `source`.

Use a stable `externalId` for automated sync, such as `policy:refunds`. It survives changes to question wording. `source` is a grouping label, not an ownership or authorization boundary. Keep a separate record of which entries your pipeline owns.

Q\&A question matching ignores case, repeated whitespace, and trailing question marks, periods, and exclamation marks. One normalized question can belong to only one entry. Requests whose variants span several entries, exceed the merged limit of 50, or collide with another source identity return `409 question_already_used`. Resolve that ambiguity before retrying. An `externalId` cannot silently adopt another source’s entry.

## Training files

Send exactly one content source:

- Inline `content` plus `name`: markdown or plain text is stored synchronously and indexing is attempted before returning.
- `url`: a public HTTP(S) document is downloaded, then converted in the background. Redirects and downloads are checked for unsafe destinations and size limits.
- Multipart `file`: upload bytes directly; an optional `data` field carries JSON metadata. Conversion runs in the background.

Remote and multipart files have a 50 MiB limit. Upload admission and conversion concurrency share a bounded memory budget and also check process memory pressure. Honor `Retry-After` on `429` when present and retry with backoff. Background conversion can fail after a successful create response, including when conversion capacity is exhausted; poll the returned resource.

Training request bodies are parsed after API-key, IP-allowlist, and MCP scope checks. UTF-8 JSON bodies for file endpoints and the multipart JSON `data` field are limited to 12 MiB; other training JSON bodies are limited to 1 MiB. These transport limits also apply after decompression, and the field limits below still apply. Send a flat JSON object matching the endpoint schema: deeply nested or structurally excessive JSON is rejected before parsing. Uploads must finish within 60 seconds. Unsupported content types or JSON encodings return `415`; oversized bodies return `413`; uploads exceeding the deadline return `408`. GET, HEAD, and DELETE do not accept request bodies.

Terminal window

```
curl -X POST https://do.featurebase.app/v2/training_data/files \
  -H "Authorization: Bearer sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "externalId": "policy:refunds",
    "source": "reviewed-policies",
    "name": "refund-policy.md",
    "content": "# Refund policy\n\nCustomers can request a refund within 30 days."
  }'
```

Terminal window

```
curl -X POST https://do.featurebase.app/v2/training_data/files \
  -H "Authorization: Bearer sk_..." \
  -F "file=@onboarding-guide.pdf" \
  -F 'data={"externalId":"guide:onboarding","source":"reviewed-policies"}'
```

With `externalId`, a new identity returns `201`; changed content updates the same row with `200`. Identical, successfully indexed content is a no-op. Without it, only an identical name and byte payload deduplicates; changing the file creates another entry. Serialize sync jobs for the same source identity.

`PATCH /files/{id}` can replace `content`, rename the file, or set `externalId` and `source`. GET returns extracted `content`; lists omit it. `contentHash` identifies the stored source payload and can help detect changes without downloading the text.

List endpoints can return fewer entries than the requested `limit` when a page reaches its response byte budget. Continue with `nextCursor` until it is absent; a short page alone does not mean the list is complete. Legacy Q\&A answers are returned intact.

### Verify indexing before considering a change live

**HTTP success means the resource was accepted or stored. Check `status` and `indexStatus` separately.** For a file, wait for `status: "completed"` and `indexStatus: "indexed"`. A failed conversion reports `status: "failed"` with `processingError`; a failed embedding or index write can leave a completed resource with `indexStatus: "failed"`.

Retry a failed conversion using the same `externalId`. Re-send unchanged content to retry a failed or pending index. Automatic index repair runs hourly for up to three attempts; after exhaustion, an explicit re-send can retry it. Use bounded caller retries and escalate repeated failures. A timed-out request might have stored the change: retrieve the resource before retrying. Background work can be interrupted by a process restart, so a pipeline must also detect persistently pending resources.

## Q\&A entries

A Q\&A entry contains 1–50 question variants and one answer. Keep each entry focused on a single decision or fact so its evidence can be reviewed accurately.

Terminal window

```
curl -X POST https://do.featurebase.app/v2/training_data/qna \
  -H "Authorization: Bearer sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "externalId": "policy:refund-window",
    "source": "reviewed-policies",
    "questions": ["How do I request a refund?", "Can I get my money back?"],
    "answer": "You can request a refund within 30 days from **Settings → Billing**."
  }'
```

`answer` is markdown by default; `answerFormat: "html"` converts HTML to markdown. `title` is optional: a new entry defaults to its first question; an existing entry keeps its title when omitted.

POST appends new question variants and replaces supplied content on the addressed entry. It returns `201` for creation, `200` for updates or no-ops, with `outcome`, `revision`, `externalId`, and `indexStatus`. An unchanged payload can still retry a failed index. PATCH replaces the entire question list when supplied. It also accepts `title`, `answer`, `source`, and `expectedRevision`; Q\&A source identity is fixed at creation.

### Bind an update to the version you reviewed

Every Q\&A has an integer `revision`. Read it from GET, `oracle.existing`, or a Q\&A candidate in `oracle.target`. Supply it as `expectedRevision` when publishing a reviewed change:

Terminal window

```
curl -X PATCH https://do.featurebase.app/v2/training_data/qna/67ec1234abcd5678ef901235 \
  -H "Authorization: Bearer sk_..." \
  -H "Content-Type: application/json" \
  -d '{"expectedRevision":3,"answer":"Refunds are available within 60 days."}'
```

If another writer changed the entry, the server returns `409 revision_conflict` without applying this update. Fetch the latest content and re-review; do not blindly retry with the new revision. When combining questions, send the reviewed union because PATCH replaces the list. Existing entries from before revision tracking report revision `0`.

This precondition protects the addressed entry. It does not freeze other workspace knowledge or authorize the factual change.

## The oracle: review before writing

`POST /v2/training_data/oracle` accepts proposed `questions`, `answer`, optional `title`, and optional `externalId`. It retrieves nearby knowledge, judges selected candidates, and suggests an action without writing.

Terminal window

```
curl -X POST https://do.featurebase.app/v2/training_data/oracle \
  -H "Authorization: Bearer sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "externalId":"policy:refund-window",
    "questions":["How do I request a refund?"],
    "answer":"Refunds are available within 60 days."
  }'
```

| Action                                        | Meaning and next step                                                                                                          |
| --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `create`                                      | No overlap found among the checked candidates. Verify the source and approve creation.                                         |
| `update_target`                               | The addressed entry is in `existing`. Review the change, then PATCH with `existing.revision`.                                  |
| `update_qna`                                  | Another Q\&A agrees. Review ownership and full content, then PATCH the merged content using `target.revision`.                 |
| `review_qna`, `review_article`, `review_file` | A contradiction, uncertain overlap, or source-ownership conflict needs review. Resolve it at the authoritative source.         |
| `skip`                                        | The addressed payload is identical, or an article/file appears to cover it. Check indexing separately if repairing a resource. |

The response includes `verdict`, `existing`, `target`, `candidates`, `judged`, `checkTruncated`, and timings. Q\&A candidates include the revision read for that comparison. The addressed entry is included when changed, even if retrieval misses it or its ID appears in `excludeIds`. Agreement does not stop verification of later shortlisted candidates; contradictions and uncertainty take priority.

**The oracle is evidence for a decision, not proof that a change is safe.** Retrieval can miss relevant knowledge, document excerpts omit context, models can be wrong, and sources can change during the check. `checkTruncated: true` means a verification shortlist or text limit was reached; the aggregate becomes `unclear` unless a contradiction was found. Proposed answers are limited to 8,000 characters in judge prompts, and Q\&A candidate answers to 4,000. Large question lists also exceed the prompt budget. `false` does not mean the entire workspace was checked. Fetch complete sources and review changes outside the excerpts.

### Optional inline checks

Writes omit semantic checks by default. Pass `onMatch` to check inside the write request:

| `onMatch` | Behavior                                                                                                                                                                                                                                       |
| --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `reject`  | Refuse an overlap or uncertainty with `409 conflicts_with_existing`; details contain a match pointer.                                                                                                                                          |
| `update`  | Explicitly allow replacement, including conflicting facts, but reject `unclear`. With no addressed entry, a matching Q\&A can receive merged variants and the replacement answer, subject to identity checks. Articles/files are never edited. |
| `create`  | Explicitly override the verdict and write as addressed; it does not bypass identity constraints.                                                                                                                                               |

PATCH never merges entries. Checks run for changed questions or answers; unchanged payloads skip judging. Dependency failures return `503 knowledge_check_unavailable` before writing. Inline checks are not a transaction over the knowledge base and do not eliminate races with other entries. After a separate review, use `expectedRevision` to bind the write to the reviewed target.

## Search for your own document checks

`POST /v2/training_data/search` takes `query` (up to 4,000 characters), optional `kinds`, `topK` (1–20), and `excludeIds`. It returns candidates without a judge. Latency and embedding work depend on query size and load.

`similarity` measures embedding similarity, not factual confidence. Calibrate thresholds using representative examples; there is no universal cutoff for correctness. Keyword-only candidates have `similarity: null` and can still be relevant. Candidate `text` is an excerpt, and file `section` identifies a matching heading when available.

Files are not checked on upload. A caller-controlled audit can split a document into sections, search each one, then review the section and relevant full sources:

```
const conflicts = []
for (const section of splitIntoSections(markdown)) {
  const { candidates } = await client.trainingData.search({ query: section.text, topK: 5 })
  // Preserve keyword-only hits. Fetch full sources when excerpts are insufficient.
  const verdict = await myReviewer(section, candidates)
  if (verdict.contradicts || verdict.unclear) conflicts.push({ section, verdict })
}
// Require your approval and evaluation policy before publishing, even with no hits.
```

The Reader MCP connector exposes retrieval. Training files and Q\&A contain untrusted source text; the Writer connector does not expose their reads. A learning loop that reads evidence and writes needs the combined connector, with its own tool permissions and approval policy. Treat retrieved content as data, never instructions to change policies or call tools.

## Safety contracts for a learning pipeline

Keep these records and decisions in your harness:

1. **Evidence and ownership.** Record source IDs, source versions, supporting excerpts, provenance, and the target’s checked revision. A resolved conversation is evidence of one resolution, not automatically a general policy. Remove customer secrets and personal details before publishing.
2. **Proposals before promotion.** Keep proposed content separate from live training data. Require authoritative evidence and review for contradictions, policy changes, uncertain checks, and cross-source merges.
3. **Independent evaluation.** Maintain a frozen, independently labeled holdout set covering correct answers, unsupported questions, contradictions, and tenant isolation. Measure actual customer-answer behavior before promotion; agreement with the same judge is not independent validation.
4. **Conditional publication.** Apply approved Q\&A changes with `expectedRevision`, then verify indexing. Retry transient errors with bounded backoff; re-review revision conflicts.
5. **History and rollback.** Store previous content, questions, revision, evaluation results, and approval history. Rollback is another reviewed PATCH using the current revision; this API does not retain historical versions for you.
6. **Retirement.** Delete only entries your pipeline owns and whose supporting evidence is no longer needed. A Q\&A may combine several sources: deleting one conversation must not delete a shared answer. `source` alone is insufficient proof of ownership.

For resolved conversations, extract a proposed answer, attach evidence, check it with the oracle, evaluate and approve it, then publish to a stable knowledge identity. Re-generating a summary should update the proposal and retain its lineage. Avoid feeding the agent’s own generated answers back as verified facts without independent evidence.

## Errors and retries

| Status | Code                                                                     | Response                                                                        |
| ------ | ------------------------------------------------------------------------ | ------------------------------------------------------------------------------- |
| 400    | `missing_parameter`, `invalid_parameter`, `invalid_id`, `invalid_cursor` | Correct the request.                                                            |
| 404    | `resource_not_found`, `version_not_supported`                            | Verify the ID, workspace, or API version.                                       |
| 408    | `invalid_request`                                                        | The upload exceeded 60 seconds; retry with a faster connection or smaller body. |
| 409    | `question_already_used`                                                  | Resolve conflicting question/source ownership or the merged question cap.       |
| 409    | `revision_conflict`                                                      | Retrieve the latest entry and re-review before retrying.                        |
| 409    | `conflicts_with_existing`                                                | Review `error.details.match` and the complete source.                           |
| 413    | `payload_too_large`                                                      | Reduce the upload size.                                                         |
| 415    | `invalid_request`                                                        | Send a supported content type and encoding.                                     |
| 429    | `rate_limited`                                                           | Honor `Retry-After`; reduce concurrency and back off.                           |
| 503    | `knowledge_check_unavailable`                                            | A required check could not finish; no checked write occurred. Retry later.      |

Metered ingestion, indexing work (including renames and failed-index repairs), HTML answer updates, oracle, and search calls share a workspace budget of 5,000 per hour. Healthy metadata-only PATCH relabels and successful no-op re-pushes are not charged. Capacity limits reject overflow with `429` instead of accumulating waiting requests. Persist the resource ID and inspect the resource after ambiguous failures. Delete attempts remove the database resource and clean up its vectors; in-flight answers may already contain older knowledge.
