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

# Versioning and editing assets

> Preserve asset identity and original uploads while saving, replacing, and reverting edited versions through the API

An asset is a photo or video in your library, identified by a single asset ID.
A **version** is a stored file belonging to that asset. Saving an edited photo
as a version keeps the asset's ID, album memberships, metadata, and face and
person associations.

## Original and derived versions

There are two categories of asset versions:

* **Original version**: the file you first uploaded, preserved unchanged. Its
  `kind` is `original`.
* **Derived version**: a new file produced by editing or processing the photo.
  It contains the finished image and parameters describing how it was produced.
  Its `kind` identifies the producer: `edit` for a client-rendered edit, or
  `external:<service>` for an external producer.

The **current version** is the one Gumnut shows when you browse the asset or
request its image URLs. Before you save an edit, the original is current.
After you save an edit, the derived version is current, and the original remains
available to download or restore. Either an original or a derived version can
be current.

Gumnut keeps the original and at most one derived version per asset. Your first
edit adds a derived version; saving another edit replaces that derived version
with a new one. For example, if you crop a photo and later change the crop,
Gumnut keeps the untouched upload and the latest cropped image.

Each retained version has its own version ID and a `position`. Position `0` is
the original, and the highest retained position is current. Version IDs identify
specific stored files; positions can be reused after deletion or revert.

<Warning>
  Replacing a derived version or reverting to the original permanently removes
  the current derived version.
  There is no undo history of earlier saved edits. Download a version first if
  you need to keep it separately.
</Warning>

## How editing works

The Photos API stores **finished image files** as derived versions. Your
application applies the crop, rotation, filter, or external editing operation
and uploads the resulting image. Sending a recipe alone does not cause the
Photos API to render it.

## Prerequisites

Before starting, you need:

* An [API key](/guides/authentication/api-keys) scoped to the photo's library.
  Reading versions requires `read`; adding or replacing versions requires
  `write`; deleting or reverting versions requires `delete_permanently`.
* The photo's asset ID and a fully rendered JPEG or PNG, with orientation and
  all edits already applied to its pixels. The file limit is 100 MiB, including
  any metadata the API adds during finalization.
* `curl` to run the examples below. Set `GUMNUT_API_KEY` to your key and
  `ASSET_ID` to an existing asset ID.

## Read the retained versions

```bash theme={null}
curl --fail-with-body \
  "https://api.gumnut.ai/api/assets/${ASSET_ID}/versions?include=variants" \
  -H "Authorization: Bearer ${GUMNUT_API_KEY}"
```

The response is an array ordered by `position`: position `0` is the original
upload, and the last entry is current. Each entry has an `id`, `kind`, `params`,
and file details such as dimensions and size. `include=variants` adds URLs
beyond the default thumbnail, including `version_urls.original.url` for that
version's exact stored bytes.

Normal asset responses and `asset_urls` describe the **current version**.
Even `asset_urls.original` refers to the current version's stored file. To
retrieve the untouched upload after an edit, use the position-0 version's
`version_urls.original.url` instead.

## Add or replace a derived version

When the original is current, upload your edited image to add a derived version.
This example assumes `edited.jpg` already contains the original rotated
90 degrees clockwise:

```bash theme={null}
curl --fail-with-body -X POST \
  "https://api.gumnut.ai/api/assets/${ASSET_ID}/versions?include=variants" \
  -H "Authorization: Bearer ${GUMNUT_API_KEY}" \
  -F 'file=@edited.jpg;type=image/jpeg' \
  -F 'kind=edit' \
  -F 'params={"version":1,"angle":90,"mirror":false}'
```

When a derived version is already current, set `CURRENT_VERSION_ID` to its `id`
from the version list and replace it:

```bash theme={null}
curl --fail-with-body -X POST \
  "https://api.gumnut.ai/api/assets/${ASSET_ID}/versions/${CURRENT_VERSION_ID}/replace?include=variants" \
  -H "Authorization: Bearer ${GUMNUT_API_KEY}" \
  -F 'file=@edited.jpg;type=image/jpeg' \
  -F 'kind=edit' \
  -F 'params={"version":1,"angle":90,"mirror":false}'
```

The multipart fields are `file`, `kind`, and `params`. `params` is a JSON object
serialized as a string; the API stores it without interpreting its editing
instructions. Use `kind=edit` with the compatible recipe below for geometric
edits, or `external:<service>` with your producer's own parameters for other
derived versions. `original` is reserved for the uploaded source.

For interoperability with Gumnut and Immich editors, the version-1 recipe has:

* `version`: `1`.
* `angle`: `0`, `90`, `180`, or `270` degrees clockwise.
* `mirror`: a boolean for a horizontal flip after rotation.
* Optional `crop`: integer `x`, `y`, `width`, and `height` in the base image's
  display-oriented pixels, before rotation. Keep the rectangle within the image
  and use positive width and height. Omit `crop` when there is no crop.

Render in the order **crop → rotate → horizontal mirror**. A vertical flip is
represented by a 180-degree rotation combined with a horizontal mirror.
The finished image must match the recipe; the API does not check that match.
The built-in editors refuse unfamiliar recipe fields rather than discard them
on the next save.

A version produced by an external service remains viewable and downloadable,
but the built-in editors cannot reopen it as an adjustable geometric edit.

## Restore the original

Set `ORIGINAL_VERSION_ID` to the `id` of the position-0 version, then revert:

```bash theme={null}
curl --fail-with-body -X POST \
  "https://api.gumnut.ai/api/assets/${ASSET_ID}/versions/${ORIGINAL_VERSION_ID}/revert" \
  -H "Authorization: Bearer ${GUMNUT_API_KEY}"
```

Revert makes the addressed retained version current and permanently removes
its descendants. Alternatively, `DELETE
/api/assets/{asset_id}/versions/{version_id}` removes the current non-original
version and restores its predecessor. You cannot delete the original through
that endpoint.

## Handle saves and conflicts

* A new append or replacement returns `201`. An exact retry with the same
  kind, parameters, and finalized bytes returns the current version with `200`
  and stores nothing new.
* A stale append or replacement returns `409`. Refetch the asset and versions
  before deciding whether to replace the latest edit; do not blindly overwrite
  someone else's changes.
* `422` means the body, metadata, or image is invalid. `507` means retaining
  the version would exceed storage capacity. Every retained version, including
  the original, counts toward storage use.
* The API copies metadata from the original during finalization, so stored
  bytes can differ from your upload. Use the returned version's checksum and
  URLs when tracking the saved result.

See the **API Reference** tab for endpoint schemas and error responses.

## Faces on edited photos

Face boxes are anchored to the original photo's pixel coordinates. While a
derived version is current, Gumnut and the Immich adapter hide those boxes
and reject new manual boxes rather than draw or save them in the wrong place.
Your existing face and person associations remain attached to the asset.
Restoring the original makes its face geometry available again.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.