---
title: "Append an asset version"
method: POST
path: "/api/assets/{asset_id}/versions"
tags: ["assets"]
---

# Append an asset version

`POST /api/assets/{asset_id}/versions`

## Request

- Base URL: `https://api.gumnut.ai`
- URL: `https://api.gumnut.ai/api/assets/{asset_id}/versions`
- Auth: one of:
  - HTTP bearer
  - HTTP bearer

Uploads a fully rendered image — with all edits already applied to its pixels — above the original version. An append is accepted only while the original is current; replace the current derived version through its replacement endpoint instead. Exact duplicates store nothing. Metadata is copied from the original, so retained bytes may differ from uploaded bytes.

## Path parameters

- `asset_id` string, required: Asset ID (with `asset_` prefix) to add the version to.

## Query parameters

- `include` string[], nullable: Optional response expansion. The single accepted value is `variants`: without it each row's `version_urls` carries only its lean thumbnail rung; with it, every rung plus the signed exact-byte `original`. Accepts multiple `include=` query params or a single comma-delimited value. Unknown values return 422.

## Request body `multipart/form-data`

- object
  - `file` string, binary, required: JPEG or PNG bytes with all edits and orientation already applied to the pixels, at most 100 MiB. The metadata-finalized file must also remain within that limit. The part's `Content-Type` declares the expected format; the format detected from the bytes is authoritative, and a concrete declared type that disagrees with it returns 422.
  - `kind` string, required: What produced this rendering: `edit` for an edit rendered by the client, or `external:<service>` for an external producer. `original` is reserved for the uploaded position-0 version.
  - `params` string, required: JSON object describing how the rendering was produced (e.g. an edit recipe), serialized as a string. Opaque to the server; the schema is defined by whichever producer sets `kind`. Malformed JSON, a non-object, or an over-large object returns 422.

## Response `200`

The upload duplicates the current version (same `kind`, `params`, and bytes); that version is returned and nothing new is stored.

- AssetVersionResponse: One rendering in an asset's retained version chain.
  - `id` string, required: Unique version identifier with 'asset_version_' prefix
  - `position` integer, required: Zero-based index in the chain: 0 is the uploaded original, the highest is the current rendering.
  - `kind` string, required: What produced this rendering: `original` (the upload), `edit` (an edit rendered by the client), or `external:<service>`. The namespace is open — treat an unrecognized kind as opaque rather than failing.
  - `params` object, nullable: How this rendering was produced (e.g. an edit recipe). Opaque to the server; the schema is defined by whichever producer sets `kind`. `null` only for the original.
  - `mime_type` string, required: MIME type of this rendering's bytes (e.g., 'image/jpeg')
  - `width` integer, required: Width of this rendering in pixels
  - `height` integer, required: Height of this rendering in pixels
  - `file_size_bytes` integer, required: Byte size of this rendering's stored bytes.
  - `checksum` string, nullable, required: Base64-encoded SHA-256 hash of this rendering's stored bytes, for comparing a locally computed hash against the chain (e.g. when reconciling an ambiguous create failure). Not unique: identical bytes may legitimately appear at different positions or on other assets. Transitionally null for roots written during the column's rollout window, until a follow-up backfill lands.
  - `version_urls` object, nullable: URLs for this rendering, shaped like an asset's `asset_urls`: the lean `thumbnail`/`thumbnail_image` rung by default; `include=variants` adds the remaining rungs and `original`, this rendering's exact stored bytes.

## Other responses

- `201`: Successful Response
- `401`: Missing, invalid, or expired credentials.
- `403`: The credentials are valid but not authorized for this operation — for example an API key whose action or library scope excludes it, or a credential type this operation does not accept.
- `404`: Not found
- `409`: The requested append or replacement does not match the asset's current state. Refetch the asset before choosing the next action. Clerk sign-in could not be associated with the existing account. Resolve the identity conflict before retrying sign-in; repeated requests with unchanged verification and identity state will not resolve it.
- `422`: The multipart body, version metadata, or uploaded image is invalid.
- `429`: Rate limit exceeded, or too many of your requests are already in progress. Retry after the interval in the `Retry-After` header.
- `502`: Transient storage or metadata-finalization error. Retryable: retry the upload after the interval in the `Retry-After` header. The detail carries a stable `error_code` (`"transient_storage_error"` or `"metadata_finalization_error"`) plus `asset_id` and `part_number` keys (possibly null).
- `503`: Authentication provider temporarily unavailable. Retry sign-in later.
- `507`: Retaining the new version would exceed the account or library storage cap. Not retryable until space is freed or the cap is raised.

## Changes

- **2026-10-16** `7c2860d9eb88`: 1 info
  - added the non-success response with the status `503`
- **2026-08-18** `2c0ab3f1b985`: 1 info
  - endpoint added

[Operation history](https://skmtc.dev/gumnut-ai/apis/gumnut-api/changes/api/assets/:asset_id/versions/post.md)

---

[API](https://skmtc.dev/gumnut-ai/apis/gumnut-api.md) - [All operations](https://skmtc.dev/gumnut-ai/apis/gumnut-api/llms.txt) - [OpenAPI document](https://skmtc.dev/gumnut-ai/apis/gumnut-api/revisions/4dcbf19e1db8?raw)
