> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Knowledge Base Document Versioning and Rollback

> Read a document’s version lineage, diff two versions before promoting a change to retrieval, and roll back to a known-good snapshot when a re-upload or connector sync degrades answer quality.

# Knowledge Base Document Versioning and Rollback

Re-uploading a document used to be a one-way door. Post a file under an
existing name and the previous content was deleted along with its chunks
and retrieval vectors — so a bad re-index or a broken connector sync could
silently degrade the answers your AI agent grounds in, with no way back.
Document versioning closes that gap: every time a document is replaced,
the superseded version is captured as an immutable snapshot you can
inspect, diff, and roll back to.

Agents have carried version history for a while; their knowledge now does
too. This guide walks the full flow: when versions are captured, how to
read the lineage, how to diff before you promote a change, how to roll
back, and the retention ceilings and guard rails that bound the feature.

The endpoint paths below are relative. Send them against
`https://api.orbit.devotel.io/api/v1`. This guide assumes a knowledge base
with documents already loaded — see
[Build and Maintain an AI Knowledge Base](/guides/knowledge-base-lifecycle)
for the create/load/attach flow.

## When a new version is captured

A version snapshot is written every time a document's content is replaced
under the same identity. Three paths replace content:

* **Content re-upload.** Posting a document with the same knowledge base
  and name again (via
  `POST /knowledge-bases/:id/documents`) replaces the existing document.
  The replaced content is snapshotted first, then the new upload becomes
  the new head version.
* **Connector and refresh syncs.** A managed connector resync, a scheduled
  refresh of a URL/RSS/Notion source, or a forced
  `POST /knowledge-bases/:id/refresh` that pulls changed content goes
  through the same replace path — so connector drift never destroys the
  last known-good copy.
* **Rollback.** Restoring an earlier version is itself a content replace,
  so the version you rolled back *from* is snapshotted too. Nothing is
  ever destroyed.

Manual redos and partial edits count as re-uploads: any second upload
under the same name snapshots the prior content, whether it was better or
worse. Versioning is automatic — there is no flag to set, and no version
is created for the very first upload (there is nothing to supersede).

## Read a document's version lineage

List the current head plus every retained snapshot of a document:

```bash theme={null}
curl https://api.orbit.devotel.io/api/v1/knowledge-bases/kb_abc123/documents/doc_refund_policy/versions \
  -H "X-API-Key: dv_live_sk_..."
```

The response carries two parts:

* `current` — the live head version, marked `is_current: true`.
* `history` — the retained snapshots of superseded versions, newest first.

Each entry (current and historical) describes one version:

* `version` — the 1-based version number. Use this in the diff and
  rollback calls.
* `name` / `type` — the document's display name and content type at that
  version.
* `size_bytes`, `chunk_count`, `char_count` — how big the version was and
  how many retrieval chunks it produced.
* `content_hash` — a normalized SHA-256 of the version's text. Two
  versions with the same hash are byte-equivalent after whitespace and
  unicode normalization, so the hash is the cheap "did this sync actually
  change anything?" check before you open a diff.
* `created_at` / `superseded_at` — when the version was first uploaded and
  when it was replaced (the uploaded-by timestamp pairing you audit
  against a deploy or sync window).
* `rollback_available` — whether that snapshot still carries its full
  content and can be restored (see the retention ceilings below).

Documents uploaded before versioning existed report as version 1 with an
empty history; their first replacement starts the lineage at version 2.

## Diff two versions

Compare any two versions before you trust a change in front of customers:

```bash theme={null}
curl "https://api.orbit.devotel.io/api/v1/knowledge-bases/kb_abc123/documents/doc_refund_policy/versions/diff?from=2&to=3" \
  -H "X-API-Key: dv_live_sk_..."
```

`from` and `to` are both required 1-based version numbers. The response is
a line-oriented diff:

* `added` / `removed` / `unchanged` — line counts, so you see the scale of
  the change at a glance.
* `hunks` — a unified-style excerpt where each line is prefixed with ` `
  (unchanged), `-` (removed), or `+` (added). Read the hunks like a code
  review: verify the policy wording that changed is the wording you meant
  to change before the new version grounds live answers.
* `truncated` — `true` when the excerpt was capped. Hunk output is capped
  at 400 lines, and for very large document pairs the endpoint degrades to
  the counts only (no hunks) rather than stall on a pathological diff.

Two rules to keep in mind:

* The head version diffs against live content; older versions diff against
  their retained snapshots. A snapshot whose content was dropped (over the
  size ceiling — see below) returns `422`, so prefer diffing against
  versions whose `rollback_available` is `true`.
* An identical `content_hash` on two versions predicts an empty diff —
  skip the call.

## Roll back to an earlier version

Restore a snapshot exactly as returned by the versions list:

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/knowledge-bases/kb_abc123/documents/doc_refund_policy/rollback \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{ "version": 2 }'
```

The endpoint returns `202` with the new document record. `202` means
*accepted, not finished*: the restored content is re-chunked and
re-indexed asynchronously — the document's previous chunks and retrieval
vectors are replaced as the rebuild runs. Poll
`GET /knowledge-bases/:id/documents` until the document's indexing status
settles back to `indexed` before benchmarking or declaring the rollback
done.

Rollback is additive, never destructive:

1. The current head is snapshotted into history first — exactly the same
   as if a human had re-uploaded over it.
2. The target version's content is then re-uploaded and becomes the new
   head version.

Because rollback goes through the normal replace path, it is fully
reversible. Rolled back too far, or to the wrong version? The version you
left is now sitting in `history` — list versions and roll back again.
Expect a `409` in two cases: the target version is already the current
head, or the target snapshot no longer carries its content
(`rollback_available: false`). An unknown version number is a `404`.

## Retention ceilings

History is bounded so the lineage stays cheap to store and fast to read:

* **Count ceiling.** At most **10** snapshots are retained per document —
  the most recent ones. A document on its 40th upload answers about
  versions 30–40; older snapshots fall off.
* **Size ceiling.** Snapshots retain their full content for rollback up to
  **1 MB** of text per version. Above that, the snapshot stays in history
  as metadata only (name, counts, hash, timestamps) with
  `rollback_available: false` — you can still diff metadata and audit what
  changed, but that specific version cannot be restored and cannot be
  content-diffed.

Retention is per document, not per knowledge base, so one thrash-y
document never eats another document's history.

## Guard rails and permissions

* **Scopes.** The two reads (`GET .../versions`, `GET .../versions/diff`)
  need the `knowledge:read` scope — listing version metadata is not a
  privileged action. The rollback (`POST .../rollback`) is a write: it
  requires `knowledge:write` plus an `owner`, `admin`, or `developer`
  role, the same guard as uploading a document.
* **Audit.** Every rollback is written to the workspace audit log with the
  document, the restored version, and the actor — rolling back the
  ground-truth an agent answers from is always traceable. Reads are not
  logged.
* **Tenancy.** Lineage lives on the document inside your organization's
  knowledge base; no other workspace can see it, and the same
  organization-scoped isolation that guards the base guards its history.
* **Reversible by design.** Because rollback itself snapshots the head,
  there is no destructive path in this feature — the worst outcome of a
  mistaken rollback is one more entry in the history list.

## Troubleshooting

| Symptom                                                 | Fix                                                                                                                                                  |
| ------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| `409` "already the current version" on rollback         | The target version is the live head. Pick an older version from the lineage, or check the diff first — the head may already equal what you wanted.   |
| `409` "content was not retained" on rollback            | That snapshot exceeded the 1 MB content ceiling and is metadata-only. Restoring it needs the original source file re-uploaded, not a rollback.       |
| `422` on a diff                                         | One of the two versions is metadata-only (content dropped by the size ceiling). Diff against versions whose `rollback_available` is `true`.          |
| `404` "Document version"                                | The version number is outside the retained window (older than the last 10 snapshots) or never existed. Re-list versions and pick a live number.      |
| History looks shorter than the deploy count             | Only the most recent 10 snapshots are kept; each re-upload past that drops the oldest one.                                                           |
| Diff returns counts but no hunks                        | The pair was too large for a line-by-line diff, so the endpoint returned the coarse summary (`truncated: true`). The counts are still accurate.      |
| Rollback returned `202` but retrieval still looks wrong | The rebuild is asynchronous. Poll the documents list until the status is `indexed`, then re-run the retrieval benchmark against the pinned baseline. |

## See also

* [Build and Maintain an AI Knowledge Base](/guides/knowledge-base-lifecycle) —
  the full create, load, attach, and retrieval-tuning flow this guide
  extends.
* [Auto-draft knowledge base articles from unanswered questions](/guides/kb-auto-draft) —
  close the gaps the lineage helps you protect.
* [Knowledge gap miner](/guides/knowledge-gap-miner) — find the clusters of
  unanswered questions whose fixes should become new document versions.
* [Agent grounding citations](/guides/agent-grounding-citations) — how the
  retrieved chunk becomes a cited answer once the right version is live.
* [Knowledge bases API reference](/api-reference/knowledge-bases) — endpoint
  shapes for the knowledge-base routes.
