---
title: "Update metadata on multiple assets"
method: POST
path: "/api/assets/bulk-update"
tags: ["assets"]
---

# Update metadata on multiple assets

`POST /api/assets/bulk-update`

Updates metadata on multiple assets in one transactional call. Each item carries the target asset id and the per-asset change — different fields can be changed on different assets in the same request. Atomic: any per-item validation failure or unknown / cross-user id rejects the whole batch and writes nothing.

Up to 200 items per request; over-cap requests return 422. For a single-asset edit, prefer `update_asset` — semantically identical but slightly more concise at the call site.

## Request body

- BulkUpdateAssetsRequest — Bulk-update request body — heterogeneous per-asset changes in one call.
  - `updates` BulkUpdateAssetItem[], required — List of per-asset updates. Each item carries the target asset id and the change to apply to it; different fields can be changed on different assets in the same request. Up to 200 items per request.
    - `id` string, required — Asset ID (with the `asset_` prefix) to apply this change to.
    - `change` UpdateAssetRequest, required — User-editable metadata for a single asset. Mirrors the field shape of ``UserAssetMetadataUpdate``; the service model owns cross-field validation. See ``photos-api/docs/references/api-design.md`` for the router-DTO + service-model pattern.
      - `description` string, nullable — User-set description for the asset. Pass ``null`` to remove a previously-set value (the response then falls back to the description embedded in the file, if any). Omit to leave unchanged. Distinct from the AI-generated `description` field on the response — this writes to `metadata.description`.
      - `latitude` number, nullable — GPS latitude in decimal degrees, ``[-90, 90]``. Must be set together with ``longitude``. Pass ``null`` (along with ``longitude=null``) to remove a previously-set value; omit to leave unchanged.
      - `longitude` number, nullable — GPS longitude in decimal degrees, ``[-180, 180]``. Must be set together with ``latitude``. Pass ``null`` (along with ``latitude=null``) to remove a previously-set value; omit to leave unchanged.
      - `original_datetime` string, date-time, nullable — When the asset was originally captured. Aware values store the offset from ``utcoffset()`` alongside; naive values store NULL offset. Pass ``null`` to remove a previously-set value — the response then falls back to the datetime embedded in the file when present, otherwise to the file's upload timestamp. Omit to leave unchanged.

## Response `200`

Successful Response

- BulkUpdateAssetsResponse — Acknowledgment body for ``POST /api/assets/bulk-update``. Empty by design; exists so MCP tools generated from this endpoint have a real ``outputSchema``. Distinct from ``DeletionResponse`` because that name is purpose-scoped to destructive operations — reusing it on a non-destructive endpoint would misname the wire shape in OpenAPI and generated SDKs.

## 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
- `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)
