---
title: "Finalize file uploads"
method: POST
path: "/api/v1/assets/finish-upload"
tags: ["Asset Management"]
---

# Finalize file uploads

`POST /api/v1/assets/finish-upload`

## Request

- Base URL: `https://api.imagestep.dev`
- URL: `https://api.imagestep.dev/api/v1/assets/finish-upload`
- Auth: API key in header `Authorization`

Creates one asset per staged object whose bytes were PUT to the staged URL.

Send only what the caller knows: which staged object, the file's name and, optionally, a collection
and tags — `[{"objectId": "…", "name": "photo.jpg", "collection": "shoot-01"}]`. The type comes from
the name's extension (the rule stage-upload signed the PUT with); size, dimensions and SHA-1 are
measured by ingest; the source is always `upload`. Every asset starts PROCESSING — poll
POST /api/v1/assets/status until it is DONE.

Every item is checked before any is written, so one bad item fails the whole request and creates
nothing. At most 500 objects per request. The asset limit is checked here, and this is the check
that counts — stage-upload's is advisory.

USE THIS WHEN:
- Finalizing file uploads after uploading to staged URLs (single or batch)

DO NOT USE WHEN:
- Staging the upload → use POST /api/v1/assets/stage-upload
- The image is at a public URL → use POST /api/v1/assets/from-url

## Headers

- `Idempotency-Key` string

## Request body

- FinishUploadRequest[]: The staged objects to turn into assets
  - `objectId` string, required: The objectId stage-upload returned for this file
  - `name` string, required: The file's name; its extension decides the type, and the asset is named after its stem, cut to 200 characters
  - `collection` string: Optional collection to put the asset in: an opaque name, no hierarchy, at most 200 characters; names starting with job: are reserved
  - `tags` string[]: Optional tags for the new asset: your own labels, matched exactly by GET /api/v1/assets?tag= (at most 50, each at most 100 characters)
  - `retentionDays` integer: Keep the asset this many days instead of your plan's retention — shorter only; more is kept for the plan's time (imagestep#591). Its `expiresAt` says what was stamped.

## Response `200`

One new asset per object, in request order

## Other responses

- `400`: - `invalid_param` on `objectId` — missing, not staged by this account, named twice in the request, already finished, or staged more than a day ago (a staged object never finished is deleted after a day) - `invalid_param` on `name` — no image extension this service accepts - `invalid_param` on `collection` or `tags` — over a limit, a control character, or a collection starting with `job:` - `invalid_param` — an empty list, or more than 500 objects
- `409`: Conflict — `error.code` is `idempotency_key_reuse` (the key already means a different request) or `request_in_progress` (the first request under this key has not settled yet; retryable).
- `422`: `asset_count_exceeded` — the objects would take the account past its plan's asset limit
- `429`: Rate limited — `error.code` is `rate_limited` and `retryable` is true. `Retry-After` says how many seconds to wait.

---

[API](https://skmtc.dev/imagestep/apis/imagestep-api.md) · [All operations](https://skmtc.dev/imagestep/apis/imagestep-api/llms.txt) · [OpenAPI document](https://skmtc.dev/imagestep/apis/imagestep-api/revisions/5e8e922a3085?raw)
