Docsary
Markdown Ask Claude Ask ChatGPT
On this page

Versions

A site holds any number of versions. Each has its own pages, its own navigation tree, and its own search index. Assets, redirects, domains, and settings are shared across the whole site.

Every page belongs to exactly one version. There is no such thing as a page outside a version.

Slugs and URLs

One version on each site is the default version. It serves from unprefixed URLs. Every other version serves under its slug.

Version Status URL
1.0 default /guide/install
0.9 other /0.9/guide/install
next other /next/guide/install

Version slugs must match ^[a-z0-9][a-z0-9._-]*$, which allows the dotted forms teams actually use: 1.0, 2.4.1, v3, next, legacy.

Two collision rules are enforced when you create a version:

The rule runs in the other direction too: a page whose top-level path segment equals a version slug is rejected at import time. The two namespaces are kept disjoint so that a URL always has exactly one meaning.

The default version

The first version created on a site becomes its default automatically. After that, you set it explicitly:

curl -s -X PATCH "$DOCSARY_API/v1/sites/acme" \
  -H "authorization: Bearer $DOCSARY_KEY" \
  -H "content-type: application/json" \
  -d '{"default_version":"1.0"}'

The default version's own prefix is not a valid URL. A request for /1.0/guide/install while 1.0 is the default is answered with a 301 to /guide/install, preserving the query string. There is exactly one canonical address for every page in the current docs, and links written with the prefix still land in the right place.

If a site has no default version — because the default one was deleted — serving falls back to the first version by sort position. The site keeps working; it just no longer has an explicit answer to "which one is current".

Promoting a release

The sequence to publish a new version without disturbing the current one:

# 1. create it — it is not the default, so it serves under its own prefix
curl -s -X POST "$DOCSARY_API/v1/sites/acme/versions" \
  -H "authorization: Bearer $DOCSARY_KEY" \
  -H "content-type: application/json" \
  -d '{"slug":"2.0","name":"2.0","position":0}'

# 2. push content into it — visible at /2.0/... while you review
node scripts/push.mjs ./docs --site acme --version 2.0 --prune

# 3. promote when ready
curl -s -X PATCH "$DOCSARY_API/v1/sites/acme" \
  -H "authorization: Bearer $DOCSARY_KEY" \
  -H "content-type: application/json" \
  -d '{"default_version":"2.0"}'

Step 2 gives you a real, servable staging URL — the new version is live at its prefix from the first push, and it is excluded from llms-full.txt until it becomes the default.

At step 3 the URL spaces swap. 2.0 moves to the unprefixed URLs, and the previous default moves from unprefixed URLs to its own prefix. Inbound links to /guide/install now resolve to the 2.0 page, which is normally the point. Readers who need the old text find it at /1.0/guide/install, permanently.

What visitors see on an old version

Three things change on any non-default version.

A banner appears above the content: "You're viewing the 1.0 docs. Switch to the current version." The link points at the same page in the default version when that page exists there, and at the site root when it does not — so switching versions from a page that was deleted does not dead-end on a 404.

The canonical URL points at the default version's copy of the same page when one exists, and at itself otherwise. Search engines consolidate ranking on the current docs instead of splitting it across every release you have ever shipped.

Search is scoped to the version being read. /1.0/search?q=install searches only the 1.0 pages, and results link back into /1.0/. A reader on old docs never gets silently handed a current-version answer. The results page offers Search all versions as a deliberate second step: it spans every release, labels the hits that are not current, and shows a page that exists in several versions once — the current one.

A version switcher appears in the header as soon as a site has more than one version. Its order follows each version's position field, then slug. Selecting an entry goes to that version's root.

Versions and the machine-readable surfaces

Surface Coverage
/sitemap.xml Every page of every version
/llms.txt Every version, with the default marked (current)
/llms-full.txt The default version only
/search One version by default; every version on request

llms-full.txt is deliberately narrow. It is the corpus an agent reads to answer questions about your product, and shipping three releases of the same page into one context window produces worse answers, not better ones.

Pruning

--prune on a push, or a prune key on an import call, deletes every page in that version that is not in the list you send:

node scripts/push.mjs ./docs --site acme --version 1.0 --prune

Prune is what makes a push a true mirror of your folder: a file deleted locally is deleted from the version. Without it, a push only creates and updates, and a renamed page leaves its old path serving forever.

The prune list is the full set of paths that should survive, so it runs as the last call after all content chunks. Deletions run in a single transaction — a prune either applies completely or not at all.

Renaming a page is a prune plus a redirect. The prune removes the old path; the redirect keeps its inbound links working.

Deleting a version

curl -s -X DELETE "$DOCSARY_API/v1/sites/acme/versions/0.9" \
  -H "authorization: Bearer $DOCSARY_KEY"

This deletes the version, all of its pages, and its search index entries. It is immediate and there is no undo — take an export first if the content matters.

If the deleted version was the site's default, the site is left with no default version.