---
title: "Manually stack assets"
method: POST
path: "/api/stacks"
tags: ["stacks"]
---

# Manually stack assets

`POST /api/stacks`

Groups two or more existing assets into a new user-owned stack (`origin = user`) for collapsed display. A user-owned stack is never re-segmented by burst re-detection.

An asset already in another stack is repointed into the new one, folding that stack in whole if it was its pinned cover; a stack left with fewer than 2 members dissolves. The photos themselves are untouched.

If a concurrent stack change invalidates the request mid-flight, it returns 409 and nothing is created; retry the request unchanged.

## Request body

- StackCreate
  - `asset_ids` string[], required — Asset IDs (with `asset_` prefix) to group into the new stack — at least 2 distinct ids, all in the target library.
  - `primary_asset_id` string, nullable — Asset ID (with `asset_` prefix) to pin as the stack's cover; must be one of `asset_ids`. Omit to leave the cover unpinned — there is no automatic pick, and clients choose their own display cover for an unpinned stack.
  - `library_id` string, nullable — Library to create the stack in. Optional if the user has a single live (non-trashed) library; required when they have multiple.

## Response `201`

Successful Response

- StackResponse — Represents a group of assets displayed as a single tile.
  - `id` string, required — Unique stack identifier with 'asset_stack_' prefix
  - `primary_asset_id` string, nullable — ID of the asset the user pinned as the stack's cover, or null if none is pinned. Null for an auto-detected burst unless a user has since pinned a cover — there is no server-selected default, so a client showing a stack with no pinned cover picks its own. A pinned cover that has been trashed keeps its ID here; it is cleared only once the asset is permanently deleted.
  - `asset_count` integer, required — Number of live assets in this stack. Excludes trashed members, so it can drop below the number of frames originally grouped.
  - `origin` 'auto_burst' | 'user', required — How a stack came to exist. `auto_burst` marks a stack the burst detector created from the time + EXIF-camera signal; `user` marks a stack a user created or edited (manual create, set-cover, add/remove, unstack). The distinction is what keeps re-detection from stomping a user's correction — the detection pass skips `user` stacks.
  - `created_at` string, date-time, required — When this stack was created
  - `updated_at` string, date-time, required — When this stack was last updated

## Other responses

- `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` — A concurrent stack change invalidated the request
- `422` — Validation Error
- `429` — Rate limit exceeded. Retry after the interval in the `Retry-After` header.

---

[API](https://skmtc.net/gumnut-ai/apis/gumnut-api.md) · [All operations](https://skmtc.net/gumnut-ai/apis/gumnut-api/llms.txt) · [OpenAPI document](https://skmtc-service-staging.skmtc.workers.dev/v1/apis/gumnut-ai/gumnut-api/versions/e71db45f5d4a/schema)
