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

# Branches, tags, and aliases

> Create writable branches, pin immutable tags, route reads through aliases, and select a ref for LambdaDB document operations.

LambdaDB data versioning provides named histories for the knowledge or memory stored in a collection. Every collection starts with a default branch named `main`.

| Ref type | Purpose                                   | Writable | Target can change           |
| :------- | :---------------------------------------- | :------- | :-------------------------- |
| Branch   | Isolated line of document history         | Yes      | Advances when writes commit |
| Tag      | Immutable name for one committed snapshot | No       | No                          |
| Alias    | Stable read name for a branch or tag      | No       | Yes, with `PATCH`           |

Refs are scoped to one collection. Branch merge, direct branch reset, ref rename, and cross-collection snapshot sharing are not part of this API. Branch, tag, and alias names must contain 3 to 52 letters, numbers, underscores, or hyphens.

## Use the SDKs

Install a [supported SDK version](/reference/sdk/introduction#data-versioning-support) and use your project's connection details from LambdaDB Cloud. These examples assume an existing `knowledge-base` collection with a `title` text field and no branch named `candidate`. Run one language example or the REST alternative below, not both against the same branch name. The Go function accepts a client configured as shown in the [Quickstart](/guides/get-started/quickstart).

<CodeGroup>
  ```python Python theme={null}
  from lambdadb import BranchSource, LambdaDB, Ref

  with LambdaDB(
      project_api_key="YOUR_API_KEY",
      base_url="YOUR_BASE_URL",
      project_name="YOUR_PROJECT_NAME",
  ) as client:
      collection = client.collection("knowledge-base")
      branch = collection.branches.create(
          "candidate", source=BranchSource.branch("main")
      ).branch
      if branch.parent_branch is not None:
          print(branch.parent_branch.name)
      if branch.head_snapshot is not None:
          print(branch.head_snapshot.snapshot_id)

      collection.docs.upsert(
          docs=[{"id": "doc-1", "title": "Candidate document"}], branch="candidate"
      )
      result = collection.docs.fetch(
          ids=["doc-1"], ref=Ref.branch("candidate"), consistent_read=True
      )
  ```

  ```typescript TypeScript theme={null}
  import { LambdaDBClient, branchRef, branchSource } from "@functional-systems/lambdadb";

  const client = new LambdaDBClient({
    projectApiKey: "YOUR_API_KEY",
    baseUrl: "YOUR_BASE_URL",
    projectName: "YOUR_PROJECT_NAME",
  });
  const collection = client.collection("knowledge-base");
  const { branch } = await collection.branches.create({
    branchName: "candidate",
    source: branchSource("main"),
  });
  if (branch.parentBranch !== null) console.log(branch.parentBranch.name);
  if (branch.headSnapshot !== null) console.log(branch.headSnapshot.snapshotId);

  await collection.docs.upsert({
    docs: [{ id: "doc-1", title: "Candidate document" }],
    branch: "candidate",
  });
  const result = await collection.docs.fetch({
    ids: ["doc-1"],
    ref: branchRef("candidate"),
    consistentRead: true,
  });
  ```

  ```go Go theme={null}
  import (
      "context"
      "fmt"

      lambdadb "github.com/lambdadb/go-lambdadb"
  )

  func versioningExample(ctx context.Context, client *lambdadb.Client) error {
      collection := client.Collection("knowledge-base")
      branch, err := collection.Branches().Create(ctx, lambdadb.CreateBranchInput{
          BranchName: "candidate",
          Source:     lambdadb.BranchSource("main"),
      })
      if err != nil {
          return err
      }
      if branch.ParentBranch != nil {
          fmt.Println(branch.ParentBranch.Name)
      }
      if branch.HeadSnapshot != nil {
          fmt.Println(branch.HeadSnapshot.SnapshotID)
      }

      _, err = collection.Docs().Upsert(ctx, lambdadb.UpsertDocsInput{
          Docs:   []map[string]interface{}{{"id": "doc-1", "title": "Candidate document"}},
          Branch: lambdadb.String("candidate"),
      })
      if err != nil {
          return err
      }
      _, err = collection.Docs().Fetch(ctx, lambdadb.FetchDocsInput{
          Ids:            []string{"doc-1"},
          Ref:            lambdadb.BranchRef("candidate"),
          ConsistentRead: lambdadb.Bool(true),
      })
      return err
  }
  ```
</CodeGroup>

This fetch includes eligible pending writes on `candidate`; it does not prove that a snapshot has committed. Before creating a tag, verify the intended committed data with `consistentRead: false`. See [Consistent reads](#consistent-reads) for limits and the bulk-import exclusion.

The SDK functions below take an initialized client and use the same `knowledge-base` collection. Call Python functions inside the client's `with LambdaDB(...) as client:` block, before it closes. Each example states its required refs and data; they are not an automatic sequence after the write above.

## Create and list branches

Create a branch from `main`:

```bash theme={null}
curl -i -X POST \
  "$BASE_URL/projects/$PROJECT_NAME/collections/$COLLECTION_NAME/branches" \
  -H "content-type: application/json" \
  -H "x-api-key: $LAMBDADB_PROJECT_API_KEY" \
  -d '{
    "branchName": "candidate",
    "source": { "kind": "branch", "name": "main" }
  }'
```

Omit `source` to use `main`. A branch can be created only from another branch in the same collection; tag and alias sources return `400 BadRequest`. A branch created from an empty source has explicitly `null` `headSnapshot` and `parentSnapshot`, but `parentBranch` still identifies the source branch.

Branch creation copies the source's committed state. It does not copy pending writes. Schema and retention updates remain collection-scoped; tag reads use their pinned schema.

```json theme={null}
{
  "branch": {
    "name": "candidate",
    "parentBranch": {
      "branchId": "<main-branch-id>",
      "name": "main"
    },
    "headSnapshot": {
      "snapshotId": "<snapshot-id>",
      "snapshotCommittedAt": 1788335940000
    },
    "parentSnapshot": {
      "snapshotId": "<snapshot-id>",
      "snapshotCommittedAt": 1788335940000
    },
    "createdAt": 1788336000000
  }
}
```

List branches:

```bash theme={null}
curl -i \
  "$BASE_URL/projects/$PROJECT_NAME/collections/$COLLECTION_NAME/branches" \
  -H "x-api-key: $LAMBDADB_PROJECT_API_KEY"
```

The response contains a `branches` array with the same fields as the create response:

| Field                 | Meaning                                                                                                                                                              |
| :-------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `parentBranch`        | Fixed direct source branch at creation time, with `branchId` and `name`. Explicitly `null` for `main` or when no parent was recorded.                                |
| `headSnapshot`        | Current committed head, or `null` before a committed head exists.                                                                                                    |
| `parentSnapshot`      | Fixed snapshot from which the branch was created, not the previous head. `null` for `main` and branches created from an empty source, even after their first commit. |
| `snapshotCommittedAt` | Snapshot commit time in Unix epoch milliseconds, inside each non-null snapshot object alongside `snapshotId`.                                                        |
| `createdAt`           | Branch creation time in Unix epoch milliseconds, independent of snapshot commit times.                                                                               |

When created from a nonempty source, a branch initially has matching head and parent snapshots. The head can advance while the parent stays fixed. Parent metadata does not guarantee that the original snapshot remains available beyond retention. Branch responses do not have a top-level `snapshotId`.

`parentBranch` records the requested source branch, not the branch where the selected snapshot originated. For example, a branch created from `dev` records `dev` even if its snapshot originated on `main`, including when selected through `asOf`. This historical metadata does not prevent deleting the parent branch. Deleting the parent or recreating its name does not change the recorded identity.

## Point-in-time branches

Set `source.asOf` to select the latest committed snapshot at or before that Unix epoch-millisecond timestamp:

```json theme={null}
{
  "branchName": "recovery-check",
  "source": {
    "kind": "branch",
    "name": "main",
    "asOf": 1788336000000
  }
}
```

`asOf` is valid only when `source.kind` is `branch`. Missing or no-longer-retained history returns `400 BadRequest`. It selects committed history, so it cannot recover a write that was still pending at the requested time. There is no public snapshot-history-list API.

Pass a cutoff within your collection's retained history. The new `recovery-check` branch must not already exist. Python takes epoch milliseconds; TypeScript accepts a `Date`, and Go accepts `time.Time` through its source helper:

<CodeGroup>
  ```python Python theme={null}
  from lambdadb import BranchSource

  def create_historical_branch(client, cutoff_ms: int):
      return client.collection("knowledge-base").branches.create(
          "recovery-check", source=BranchSource.branch("main", as_of=cutoff_ms)
      )
  ```

  ```typescript TypeScript theme={null}
  import { LambdaDBClient, branchSource } from "@functional-systems/lambdadb";

  async function createHistoricalBranch(client: LambdaDBClient, cutoff: Date) {
    return client.collection("knowledge-base").branches.create({
      branchName: "recovery-check",
      source: branchSource("main", cutoff),
    });
  }
  ```

  ```go Go theme={null}
  import (
      "context"
      "time"

      lambdadb "github.com/lambdadb/go-lambdadb"
  )

  func createHistoricalBranch(ctx context.Context, client *lambdadb.Client, cutoff time.Time) error {
      _, err := client.Collection("knowledge-base").Branches().Create(ctx, lambdadb.CreateBranchInput{
          BranchName: "recovery-check",
          Source:     lambdadb.BranchSourceAt("main", cutoff),
      })
      return err
  }
  ```
</CodeGroup>

## Create and list tags

A tag pins one committed snapshot and cannot be retargeted:

```bash theme={null}
curl -i -X POST \
  "$BASE_URL/projects/$PROJECT_NAME/collections/$COLLECTION_NAME/tags" \
  -H "content-type: application/json" \
  -H "x-api-key: $LAMBDADB_PROJECT_API_KEY" \
  -d '{
    "tagName": "validated-2026-09",
    "source": { "kind": "branch", "name": "candidate" }
  }'
```

Omit `source` to pin the current `main` snapshot. The create response wraps the ref in `tag`; `GET .../tags` returns a `tags` array.

A tag source can be a branch or another tag in the same collection, but not an alias. Creating a tag from a tag pins the same snapshot; it does not create a chain of tags. `source.asOf` is allowed only for a branch source.

Tag responses contain `name`, `snapshotId`, `snapshotCommittedAt`, and `createdAt`. Unlike branch responses, the snapshot fields are at the top level of the tag object. `snapshotCommittedAt` is the pinned snapshot's commit time, while `createdAt` is the tag's creation time; both use Unix epoch milliseconds.

```json theme={null}
{
  "tag": {
    "name": "validated-2026-09",
    "snapshotId": "<snapshot-id>",
    "snapshotCommittedAt": 1788335940000,
    "createdAt": 1788336060000
  }
}
```

A tag cannot be created from an empty head. Verify the intended committed data with `consistentRead: false` before creating a release tag; a read that includes pending writes does not prove those writes will be captured by the tag. Ref lists are unpaginated, and there are no individual branch, tag, or alias GET routes.

## Create, retarget, and list aliases

An alias points to a branch or tag:

```bash theme={null}
curl -i -X POST \
  "$BASE_URL/projects/$PROJECT_NAME/collections/$COLLECTION_NAME/aliases" \
  -H "content-type: application/json" \
  -H "x-api-key: $LAMBDADB_PROJECT_API_KEY" \
  -d '{
    "aliasName": "production-read",
    "target": { "kind": "tag", "name": "validated-2026-09" }
  }'
```

Retarget it without changing the alias name used by readers:

```bash theme={null}
curl -i -X PATCH \
  "$BASE_URL/projects/$PROJECT_NAME/collections/$COLLECTION_NAME/aliases/production-read" \
  -H "content-type: application/json" \
  -H "x-api-key: $LAMBDADB_PROJECT_API_KEY" \
  -d '{
    "target": { "kind": "branch", "name": "main" }
  }'
```

`GET .../aliases` returns an `aliases` array. Alias objects include `aliasId`, `aliasName`, `targetKind`, `targetName`, `targetId`, `aliasRevision`, `dangling`, and epoch-millisecond `createdAt`. Request `kind` values are lowercase; response `targetKind` values are `BRANCH` or `TAG`.

Deleting a non-default branch or a tag that is referenced by any alias returns `409 Conflict`. Delete or retarget every referencing alias before deleting the target; retrying the same deletion without changing those references will not resolve the conflict. Deleting an alias does not delete its target.

Aliases bind to the target's identity, not just its name. The response retains a `dangling` field for a missing target; if such a state is encountered, reads return `400 BadRequest`. It is not the normal result of target deletion, which is blocked while aliases reference it. Recreating a target name does not repair the old identity binding. Selecting a ref that does not exist returns `404 ResourceNotFound`.

### Publish a tag through an alias with the SDKs

First verify that `candidate` contains the intended committed data using a fetch or query with `consistentRead: false`. The following example assumes that verification is complete and that neither `validated-2026-09` nor `production-read` exists. It pins a tag, creates an alias initially targeting `main`, switches that alias to the tag, and reads through the alias. For an existing alias, use only the retarget call instead of creating it again.

<CodeGroup>
  ```python Python theme={null}
  from lambdadb import AliasTarget, Ref, RefSource

  def publish_tag(client):
      collection = client.collection("knowledge-base")
      collection.tags.create(
          "validated-2026-09", source=RefSource.branch("candidate")
      )
      collection.aliases.create("production-read", target=AliasTarget.branch("main"))
      collection.aliases.retarget(
          "production-read", target=AliasTarget.tag("validated-2026-09")
      )
      return collection.docs.fetch(ids=["doc-1"], ref=Ref.alias("production-read"))
  ```

  ```typescript TypeScript theme={null}
  import { LambdaDBClient, aliasRef, branchSource, branchTarget, tagTarget } from "@functional-systems/lambdadb";

  async function publishTag(client: LambdaDBClient) {
    const collection = client.collection("knowledge-base");
    await collection.tags.create({
      tagName: "validated-2026-09", source: branchSource("candidate"),
    });
    await collection.aliases.create({
      aliasName: "production-read", target: branchTarget("main"),
    });
    await collection.aliases.retarget("production-read", {
      target: tagTarget("validated-2026-09"),
    });
    return collection.docs.fetch({ ids: ["doc-1"], ref: aliasRef("production-read") });
  }
  ```

  ```go Go theme={null}
  import (
      "context"

      lambdadb "github.com/lambdadb/go-lambdadb"
  )

  func publishTag(ctx context.Context, client *lambdadb.Client) error {
      collection := client.Collection("knowledge-base")
      _, err := collection.Tags().Create(ctx, lambdadb.CreateTagInput{
          TagName: "validated-2026-09", Source: lambdadb.BranchSource("candidate"),
      })
      if err != nil {
          return err
      }
      _, err = collection.Aliases().Create(ctx, lambdadb.CreateAliasInput{
          AliasName: "production-read", Target: lambdadb.BranchTarget("main"),
      })
      if err != nil {
          return err
      }
      _, err = collection.Aliases().Retarget(ctx, "production-read", lambdadb.RetargetAliasInput{
          Target: lambdadb.TagTarget("validated-2026-09"),
      })
      if err != nil {
          return err
      }
      _, err = collection.Docs().Fetch(ctx, lambdadb.FetchDocsInput{
          Ids: []string{"doc-1"}, Ref: lambdadb.AliasRef("production-read"),
      })
      return err
  }
  ```
</CodeGroup>

## Read from a ref

Query, fetch, and extended list request bodies accept:

```json theme={null}
{
  "ref": {
    "kind": "branch",
    "name": "candidate"
  }
}
```

`ref.kind` accepts `branch`, `tag`, or `alias`. Add this object alongside the endpoint's normal fields. Omitting `ref` reads from `main`.

The GET list endpoint has no request body. Supply `refKind` and `refName` together:

```bash theme={null}
curl -i \
  "$BASE_URL/projects/$PROJECT_NAME/collections/$COLLECTION_NAME/docs?refKind=alias&refName=production-read" \
  -H "x-api-key: $LAMBDADB_PROJECT_API_KEY"
```

Supplying only one of `refKind` or `refName` is invalid.

### Consistent reads

Query and fetch requests support `consistentRead: true` only when they directly select a branch. Tag and alias reads reject it, including an alias that currently targets a branch.

Consistent reads overlay eligible pending writes on committed data. They can return `429` when that pending payload exceeds the limit, independently of request rate limits. Pending bulk imports are excluded and become visible only after indexing commits them. List requests do not support `consistentRead`.

Page tokens record a search position, not a fixed snapshot. Branches can advance and aliases can move between pages. Use the same immutable tag on every page for a stable export, keeping filters and projection options unchanged. Query responses do not include a resolved snapshot ID.

### Iterate over a tag with the SDKs

These examples assume `validated-2026-09` already exists. Each pagination helper keeps the same tag on subsequent requests. Python pages contain document dictionaries; TypeScript and Go pages contain result items whose `doc` or `Doc` field holds the document.

<CodeGroup>
  ```python Python theme={null}
  from lambdadb import Ref

  def print_tag_documents(client):
      collection = client.collection("knowledge-base")
      for page in collection.docs.list_pages(size=100, ref=Ref.tag("validated-2026-09")):
          for document in page:
              print(document)
  ```

  ```typescript TypeScript theme={null}
  import { LambdaDBClient, tagRef } from "@functional-systems/lambdadb";

  async function printTagDocuments(client: LambdaDBClient) {
    const collection = client.collection("knowledge-base");
    for await (const page of collection.docs.listPages({
      size: 100, ref: tagRef("validated-2026-09"),
    })) {
      for (const item of page.docs) console.log(item.doc);
    }
  }
  ```

  ```go Go theme={null}
  import (
      "context"
      "fmt"

      lambdadb "github.com/lambdadb/go-lambdadb"
  )

  func printTagDocuments(ctx context.Context, client *lambdadb.Client) error {
      iterator := client.Collection("knowledge-base").Docs().ListIterator(ctx, &lambdadb.ListDocsOpts{
          Size: lambdadb.Int64(100), Ref: lambdadb.TagRef("validated-2026-09"),
      })
      for {
          page, err := iterator.Next(ctx)
          if err != nil {
              return err
          }
          if page == nil {
              return nil
          }
          for _, item := range page.Docs {
              fmt.Println(item.Doc)
          }
      }
  }
  ```
</CodeGroup>

## Write to a branch

Upsert, update, delete, and bulk-upsert request bodies accept an optional `branch` string. Omitting it writes to `main`.

```json theme={null}
{
  "branch": "candidate",
  "docs": [
    { "id": "doc-1", "title": "Candidate document" }
  ]
}
```

The value is resolved as a branch name. A tag or alias is not a valid write target.

For bulk upsert, use the same branch for both control calls:

```text theme={null}
GET  .../docs/bulk-upsert?branch=candidate
POST .../docs/bulk-upsert
{
  "objectKey": "...",
  "type": "application/json",
  "branch": "candidate"
}
```

Set `Content-Type` to the response's `type` value and send every entry in its `headers` map unchanged with the presigned `PUT`.

## Delete a ref

Delete a named ref with its type-specific route:

```text theme={null}
DELETE .../branches/{branchName}
DELETE .../tags/{tagName}
DELETE .../aliases/{aliasName}
```

The default `main` branch cannot be deleted independently from its collection; an attempt returns `400 BadRequest`. Other branches and tags cannot be deleted while any alias references them (`409 Conflict`). Delete or retarget those aliases first. Deleting a ref does not delete the collection.

The default branch cannot be changed. Deleting an already absent ref can return `404`.
