---
title: Versions
description: Create, list, update, and delete versions, with the slug rules and shadowing checks that keep version and page URLs disjoint.
order: 3
---

# Versions

A version holds pages. Every page belongs to exactly one version, and a site's default version serves from unprefixed URLs. For the behaviour this drives, see [Versions](/versions).

All endpoints require an organization API key. `:site` and `:ver` each accept a slug or an id.

## List versions

```
GET /v1/sites/:site/versions
```

```bash
curl -s -H "authorization: Bearer $DOCSARY_KEY" \
  "$DOCSARY_API/v1/sites/acme/versions"
```

```json
{
  "versions": [
    { "id": "ver_...", "slug": "2.0", "name": "2.0", "position": 0 },
    { "id": "ver_...", "slug": "1.0", "name": "1.0", "position": 1 }
  ],
  "default_version_id": "ver_..."
}
```

Ordered by `position`, then slug. Compare `default_version_id` against the list to find which one is current.

## Create a version

```
POST /v1/sites/:site/versions
```

```bash
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}'
```

| Field | Type | Required | Notes |
| --- | --- | --- | --- |
| `slug` | string | yes | Must match `^[a-z0-9][a-z0-9._-]*$`. Unique within the site. Becomes the URL prefix when this version is not the default. |
| `name` | string | no | Label shown in the version switcher and the old-version banner. Defaults to the slug. |
| `position` | number | no | Sort position in the version switcher and in list responses. Defaults to `0`. |

```json
{ "id": "ver_...", "slug": "2.0" }
```

`201` on success.

**The first version created on a site automatically becomes its default version.** Subsequent ones do not; promote them with `PATCH /v1/sites/:site` and a `default_version` field.

### Rejections

| Response | Cause |
| --- | --- |
| `400 {"error":"invalid slug"}` | Slug does not match the pattern |
| `400 {"error":"reserved slug"}` | Slug is one of the paths the serving router owns — `assets`, `search`, `v1`, `robots.txt`, `sitemap.xml`, `llms.txt`, `llms-full.txt`, `manifest.webmanifest`, `sw.js`, `__api`, `__health`, `__bench` |
| `403` with `quota: "versions_per_site"` | This site's [version ceiling](/access#the-ceilings) is already reached |
| `409 {"error":"version slug already exists"}` | The site already has this slug |
| `409 {"error":"slug would shadow existing page URLs at that path"}` | Some version of this site already has a page at that path, or beneath it |

The shadowing check is what keeps version prefixes and page paths from ever colliding. If any version has a page at `/next` or `/next/anything`, a version slug of `next` is refused. The rule is enforced in the other direction too — an import rejects a page whose first path segment matches a version slug.

The new version appears in the switcher on every page of the site straight away.

## Update a version

```
PATCH /v1/sites/:site/versions/:ver
```

| Field | Type | Notes |
| --- | --- | --- |
| `slug` | string | New slug. Same pattern and reserved-word rules as creation. |
| `name` | string | New display label. |
| `position` | number | New sort position. |

```bash
curl -s -X PATCH "$DOCSARY_API/v1/sites/acme/versions/1.0" \
  -H "authorization: Bearer $DOCSARY_KEY" \
  -H "content-type: application/json" \
  -d '{"name":"1.0 (legacy)","position":9}'
```

```json
{ "ok": true }
```

`400 {"error":"invalid or reserved slug"}` for a bad slug. `404` if the site or version does not resolve. The change is live immediately.

:::warning
Changing a slug changes every URL of a non-default version, and nothing is redirected automatically. Existing links to `/1.0/guide/install` break the moment the slug becomes `legacy`. Add [redirects](/api/redirects) for the paths that matter, or rename before the version has an audience.

The shadow check that runs at creation is not re-run on a slug change.
:::

Renaming with `name` is free — it changes only the label in the switcher and banner.

## Delete a version

```
DELETE /v1/sites/:site/versions/:ver
```

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

```json
{ "deleted": "0.9" }
```

Deletes the version, all of its pages, and its search index entries. Immediate, with no undo — [export](/api/pages#export) first if the content matters.

If the deleted version was the site's default, the site is left with **no default version**. Serving falls back to the first version by position, so the site keeps working, but nothing marks a version as current until you set one. Promote a replacement:

```bash
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 deleted version's pages stop being served immediately.
