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

# Label version history

> Read who changed a label, when, in which workflow step, and what its annotations were at each hand-off, through the Python SDK, the GraphQL API, or the Labelbox MCP server.

A label changes as it moves through a workflow: a labeler submits it, a reviewer edits it, approves or rejects it, and a rework step changes it again. Review and rework edit the same label, so the label you read today shows only its last state. Label version history keeps a version of the label at each of these hand-offs: who made it, when, in which workflow step, and the label's annotations at that moment. Compare consecutive versions of a label to see what a reviewer or a rework changed.

## Before you start

You need:

* Label version history enabled for your projects. Contact your Labelbox team to enable label version history. Versions are recorded from then on: there are none from before it was enabled.
* Permission to read the project's labels. See [Who can read versions](#who-can-read-versions).
* For the Python examples, Python SDK version 7.16.0 or later.

## What is recorded

A version is recorded for each label at each of these hand-offs:

| Action | Hand-off | Made by |
| - | - | - |
| `SUBMITTED` | A label was created from the editor: a labeler's submit. | The labeler |
| `SAVED` | An existing label was saved from the editor: a reviewer's edit in a review step, a labeler's edit in a rework step, or a later save. | The person who saved it |
| `APPROVED` | A reviewer approved the label in a review step. | The reviewer |
| `REJECTED` | A reviewer rejected the label in a review step. | The reviewer |
| `REWORK_SUBMITTED` | The data row was submitted from a rework step. | The person who submitted it |
| `SENT_TO_REWORK` | The data row was moved into a rework step by hand. | The person who moved it |

A review decision on single labels records a version of each label it names. A decision on the whole data row, a rework submit, and a move into a rework step record a version of every label on the data row.

Each version holds the label's annotations as they were committed by that hand-off, with the feature names in effect then. A reviewer who edits a label and then approves it records two versions: `SAVED`, then `APPROVED`. If the decision is recorded while the reviewer's save is still being committed, the decision's version can show the annotations from before that save. The save is then the next version.

### What is not recorded

* **Autosaves and drafts.** Only the hand-offs above record a version. Edits in the editor that were not saved, and skipped data rows, record none.
* **Earlier history.** Nothing from before label version history was enabled for the project.
* **Other changes to labels.** Labels imported through the SDK or API, and reviews of tasks outside the label editor, such as external tasks, record no version.
* **Very large labels.** A label whose annotations take more than 64 MB, or more than 16 MB compressed, is not recorded. Recording never holds up the hand-off: if a version cannot be recorded, the hand-off goes ahead without it.

Versions are never changed, and they stay readable after their label is deleted. They are erased with the data row's labels when the data row is permanently erased.

## Who can read versions

You can read the versions of the labels you can read in the project, as for the project's labels:

* Without permission to view other users' labels, you see only the versions of labels you created.
* In another organization's project, without permission to view all labels, you see only the versions of labels your organization's members created.
* Otherwise, you see the versions of all the project's labels.

Visibility follows who created the label, not who made the hand-off. Without permission to read any of the project's labels, a list of versions is refused.

## Read versions with the Python SDK

List one label's versions, oldest first:

```python Python theme={"theme":"css-variables"}
import labelbox as lb

client = lb.Client(api_key="<YOUR_API_KEY>")
project = client.get_project("<PROJECT_ID>")

for version in project.label_versions(label="<LABEL_ID>"):
    print(
        version.created_at,
        version.action,
        version.created_by_id,
        version.stage and version.stage.name,
        version.annotation_count,
    )
```

`version.label_json` holds the annotations at that moment. For a `Label` you already have, such as one from `project.labels()`, `label.versions()` lists the same versions. List the versions of a whole project, filtered by data row, user, or time:

```python Python theme={"theme":"css-variables"}
# Everything reviewers and labelers handed off in October, without annotations
october = project.label_versions(
    created_after="2026-10-01T00:00:00Z",
    created_before="2026-11-01T00:00:00Z",
    include_annotations=False,
)
for version in october:  # fetches page after page as you go
    print(version.label_id, version.action, version.created_by_id)

# One version, with its annotations
version = client.get_label_version("<VERSION_ID>")
```

`Project.label_versions()` takes these parameters, all optional:

| Parameter | Description |
| - | - |
| `label` | Only this label's versions. A label ID or a `Label`. |
| `data_row` | Only versions of labels on this data row. A data row ID or a `DataRow`. |
| `created_by` | Only versions whose hand-off this user made, such as a reviewer. A user ID or a `User`. |
| `created_after` | Only versions recorded at or after this time. A `datetime` or an ISO 8601 string. A `datetime` without a time zone is read as UTC. |
| `created_before` | Only versions recorded before this time. |
| `include_annotations` | Also read each version's annotations, `label` and `label_json`. The default is `True`. Leave them out to read metadata faster. |
| `page_size` | Versions fetched per request, up to 100. The default is 100. |

`Label.versions()` and `Client.get_label_version()` take `include_annotations` too. `Client.get_label_version()` returns `None` when the version does not exist or you cannot read its label.

The SDK raises `ValueError`, before sending anything, for a `page_size` outside 1 to 100 or a `created_after` that is not before `created_before`. It raises `lbox.exceptions.AuthorizationError` when you cannot read the project's labels.

## Version fields

| SDK | GraphQL | Description |
| - | - | - |
| `id` | `id` | The version. It never changes. |
| `label_id`, `data_row_id`, `project_id` | `labelId`, `dataRowId`, `projectId` | The label, its data row, and its project. |
| `action` | `action` | The hand-off, from [What is recorded](#what-is-recorded). In the SDK, a `LabelVersionAction`. |
| `created_at` | `createdAt` | When the version was recorded. |
| `created_by_id` | `createdById` | The user who made the hand-off. GraphQL also has `createdBy`, the user. |
| `stage` | `stage` | The workflow step the hand-off happened in, as it was then: `task_queue_id` (`taskQueueId`), `name`, and `type`, such as `MANUAL_REVIEW_QUEUE` or `MANUAL_REWORK_QUEUE`. `None` (`null`) when the data row was in no step (Done). |
| `annotation_count` | `annotationCount` | The number of annotations (features) in the version. |
| `label` | `label` | The annotations at that moment, as stringified JSON in the same shape as `Label.label`. |
| `label_json` | `labelJson` | The same annotations, parsed. |

Because `label` has the same shape as `Label.label`, you can compare a version with the label as it is now, or with the version before it, using the same code.

## Read versions with the GraphQL API

<Warning>
  Labelbox recommends the Python SDK. GraphQL APIs may change without notice: see [GraphQL API](/horizon/developer-guides/getting-started#graphql-api).
</Warning>

The `labelVersions` query lists a project's versions, oldest first:

```graphql GraphQL theme={"theme":"css-variables"}
query LabelVersions($after: String) {
  labelVersions(
    where: { projectId: "<PROJECT_ID>", labelId: "<LABEL_ID>" }
    first: 100
    after: $after
  ) {
    nodes {
      id
      action
      createdAt
      createdById
      stage { taskQueueId name type }
      annotationCount
      labelJson
    }
    nextCursor
  }
}
```

Send it to `https://api.labelbox.com/graphql` with your API key as a bearer token. `where` takes `projectId`, which is required, and optionally `labelId`, `dataRowId`, `createdById`, `createdAfter` (included), and `createdBefore` (excluded). `first` is the page size, from 1 to 100, and 100 by default.

To read the next page, send the same query again with `after` set to the previous page's `nextCursor`, until `nextCursor` is `null`. A cursor only continues the query it came from: with other filters, another project, or another organization it is refused.

`labelVersion(where: { id: "<VERSION_ID>" })` returns one version, or `null` when it does not exist or you cannot read its label.

### Reading recent versions

Versions are listed in the order they were recorded. A version is listed once its hand-off has committed, so a hand-off still committing can appear a moment later with an earlier `createdAt`. To read new versions as they come, read up to a time at least a minute in the past with `createdBefore`, and start the next read from there with `createdAfter`.

## Labelbox MCP server

Through the [Labelbox MCP server](/horizon/guides/labelbox-mcp), an AI assistant can audit a label's history for you:

* `list_label_versions` lists a project's versions, oldest first, with the same filters as the API: `labelId`, `dataRowId`, `createdById`, `createdAfter`, and `createdBefore`. It returns the versions' metadata, not their annotations.
* `get_label_version` reads one version, with a summary of its annotations at that moment: their names and text answers, for up to 200 annotations. It never returns geometry, masks, or the full label. Read those with the SDK or the API.

The `labelbox://guides/label-history` guide shows the assistant how to find a label, page its versions, and compare them. Ask, for example:

```text theme={"theme":"css-variables"}
Show me how the reviewers changed the labels on data row listing-4471.jpg in the Listing photos project.
```

The tools need a connection that can read project quality data, and the same permissions to read labels as the API. Neither tool edits a label or restores a version.
