---
title: "Update an asset"
method: PATCH
path: "/api/assets/{asset_id}"
tags: ["assets"]
---

# Update an asset

`PATCH /api/assets/{asset_id}`

Edits the user-editable metadata for a single asset — description, GPS coordinates, and original capture datetime. Only fields included in the request body are changed; others are left untouched. Passing `null` for a field removes a previously-set value; the effective response may still contain a value from another metadata source. `latitude` and `longitude` must be set together (both written or both cleared).

Setting or clearing GPS coordinates schedules an asynchronous refresh of derived location names.

For editing multiple assets in one round trip, prefer `bulk_update_assets`.

## Path parameters

- `asset_id` string, required — Asset ID (with `asset_` prefix) of the asset to update.

## Request body

- UpdateAssetRequest — User-editable metadata for a single asset.
  - `description` string, nullable — User-set description for the asset. Pass `null` to remove a previously-set value; the effective response may still contain a description from another metadata source. 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. Timezone-aware values preserve their UTC offset; timezone-naive values have no offset. Pass `null` to remove a previously-set value; the effective response may still contain a datetime from another metadata source. Omit to leave unchanged.

## Response `200`

Successful Response

- AssetResponse — Represents a photo or video asset with metadata and access URLs. Top-level fields describe the asset's current rendering (the version identified by `current_version_id`) unless they explicitly name the original upload, like `original_file_name` and the `file_data` group.
  - `id` string, required — Unique asset identifier with 'asset_' prefix
  - `mime_type` string, required — MIME type of the current rendering (e.g., 'image/jpeg', 'video/mp4').
  - `original_file_name` string, required — Filename the asset was uploaded under.
  - `local_datetime` string, date-time, required — When the photo/video was taken, in the device's local timezone
  - `file_data` FileDataResponse — File/provenance scalars describing the uploaded *file* (not its content). Returned only when requested via `include=file_data`; the whole object is `null` otherwise. When present, every field carries its real value — `checksum_sha1` is the lone exception (`null` for legacy rows that never had a SHA-1). This nested object is the home for the file/provenance group.
    - `device_asset_id` string, required — Original asset identifier from the device that uploaded this asset.
    - `device_id` string, required — Identifier of the device that uploaded this asset.
    - `file_created_at` string, date-time, required — When the file was created on the uploading device.
    - `file_modified_at` string, date-time, required — When the file was last modified on the uploading device.
    - `checksum` string, required — Base64-encoded SHA-256 hash of the asset contents for duplicate detection and integrity.
    - `checksum_sha1` string, nullable — Base64-encoded SHA-1 hash of the asset contents. `null` for older assets that have no SHA-1.
    - `file_size_bytes` integer, required — Size of the uploaded file in bytes. Each rendering's own size is on its row in the asset's version listing.
  - `created_at` string, date-time, required — When this asset record was created in the database
  - `updated_at` string, date-time, required — When this asset record was last updated
  - `metadata` MetadataResponse — Metadata for an asset — camera/EXIF fields, GPS, and location names.
    - `asset_id` string, required — ID of the asset this metadata belongs to
    - `created_at` string, date-time, required — When this metadata record was created
    - `updated_at` string, date-time, required — When this metadata record was last updated
    - `make` string, nullable — Camera manufacturer (e.g., 'Canon', 'Nikon')
    - `model` string, nullable — Camera model (e.g., 'EOS 5D Mark IV')
    - `orientation` integer, nullable — Image orientation value (1-8) indicating rotation/flip: 1=normal, 2=mirror horizontal, 3=rotate 180°, 4=mirror vertical, 5=mirror horizontal+rotate 90° CW, 6=rotate 90° CW, 7=mirror horizontal+rotate 90° CCW, 8=rotate 90° CCW
    - `raw_width` integer, nullable — Pre-rotation raw width; null when not available
    - `raw_height` integer, nullable — Pre-rotation raw height; null when not available
    - `modified_datetime` string, date-time, nullable — When the file was last modified, with timezone offset if available
    - `original_datetime` string, date-time, nullable — When the photo was originally taken, with timezone offset if available
    - `digitized_datetime` string, date-time, nullable — When the photo was digitized, with timezone offset if available
    - `lens_model` string, nullable — Lens model used (e.g., 'EF 24-70mm f/2.8L II USM')
    - `f_number` number, nullable — Aperture f-stop value (e.g., 2.8, 5.6)
    - `focal_length` number, nullable — Focal length in millimeters
    - `iso` integer, nullable — ISO sensitivity value (e.g., 100, 800, 3200)
    - `exposure_time` number, nullable — Shutter speed in seconds (e.g., 0.001 for 1/1000s)
    - `exposure_bias` number, nullable — Exposure compensation in EV (e.g., -1.0, +0.5)
    - `latitude` number, nullable — GPS latitude in decimal degrees
    - `longitude` number, nullable — GPS longitude in decimal degrees
    - `altitude` number, nullable — GPS altitude in meters
    - `city` string, nullable — City name
    - `state` string, nullable — State/province name
    - `country` string, nullable — Country name
    - `country_code` string, nullable — ISO 3166-1 alpha-2 country code (e.g., 'US', 'JP')
    - `sublocation` string, nullable — Neighborhood or district
    - `place_name` string, nullable — Landmark or point-of-interest name
    - `timezone` string, nullable — IANA timezone identifier (e.g., 'America/Los_Angeles')
    - `display_label` string, nullable — Human-readable location label. Picks the most specific available identifier (place_name > sublocation > city > country) and appends broader context (city, then state-or-country). Example: 'Golden Gate Bridge, San Francisco, California'. Null when no location fields are populated.
    - `description` string, nullable — Image description or caption
    - `fps` number, nullable — Frame rate for video files
    - `live_photo_cid` string, nullable — Live photo content identifier
    - `projection_type` string, nullable — Projection type (e.g., for 360° photos)
    - `auto_stack_id` string, nullable — Identifier for automatic photo stacking
    - `rating` integer, nullable — User or camera rating (typically 1-5 stars)
  - `metrics` object, nullable — ML-generated quality scores and other metrics. `null` when not requested via `include=metrics`.
  - `asset_urls` object, nullable — Named asset variants. Images: 'original', 'thumbnail', 'small', 'preview', 'fullsize'. Videos: 'original', plus 'thumbnail_image', 'small_image', 'preview_image', 'fullsize_image' pointing at the extracted still. 'original' is served with a Content-Disposition attachment header (signed 'dl' filename param) so a top-level navigation saves it to disk, while inline subresource loads (<video>, fetch) still render it. Variant URLs are stable: a derived variant may briefly 404 until its artifact is generated, then serve from the same URL.
  - `description` string, nullable — AI-generated description of the asset's content, quality, and composition. null means description generation has not yet run; empty string means the model refused to describe the asset. Distinct from metadata.description (camera-embedded EXIF metadata).
  - `thumbhash` string, nullable — Base64-encoded ThumbHash placeholder (~28 chars). Clients decode with the `thumbhash` library (JS / Swift / Kotlin) to render an instant blurred preview before the CDN thumbnail arrives. `null` while generation is pending.
  - `faces` FaceResponse[], nullable — All faces detected in this asset. `null` when not requested via `include=faces`; `[]` when requested but the asset has no faces.
    - `id` string, required — Unique face identifier with 'face_' prefix
    - `asset_id` string, required — ID of the asset containing this face
    - `person_id` string, nullable — ID of the person this face belongs to (if identified)
    - `bounding_box` object, required — Face location as {x, y, w, h} coordinates in pixels
    - `confidence` number, nullable — Detector confidence on a 0-1 scale; higher is more confident among faces detected under the same configuration (values are not comparable across detector generations). Null on legacy faces without a stored score and on manually added faces.
    - `source` 'automatic' | 'manual', required — How this face was added: 'automatic' for detector-found faces, 'manual' for user-drawn face boxes.
    - `timestamp_ms` integer, nullable — For video files, timestamp in milliseconds when face appears
    - `asset_urls` object, nullable — Asset variants for this face: 'thumbnail' with face crop
    - `created_at` string, date-time, required — When this face was detected and recorded
    - `updated_at` string, date-time, required — When this face record was last updated
    - `cluster_assignment` ClusterAssignmentResponse — Per-face cluster-assignment diagnostics: how well the face fits its currently-assigned Person, and which other Persons are nearby in embedding space. Surfaced via `include=cluster_assignment` on the faces endpoints.
      - `distance_to_person` number, nullable — Cosine distance from the face's embedding to its currently-assigned Person's centroid. Lower = better fit. Null when the face is unassigned or when the assigned Person has no centroid.
      - `candidates` FaceCandidatePersonResponse[] — Eligible nearby Persons in the same library, sorted ascending by distance. Excludes the face's currently-assigned Person (its distance is in `distance_to_person`). Empty when there are no eligible nearby Persons.
        - `person_id` string, required — Person ID (with 'person_' prefix) of the candidate.
        - `name` string, nullable — Display name of the candidate Person, or null for an unnamed Person.
        - `distance` number, required — Cosine distance from the face's embedding to this Person's centroid (lower = closer).
  - `people` PersonResponse[], nullable — All unique people identified in this asset (deduplicated from faces). `null` when not requested via `include=people`; `[]` when requested but none are identified.
    - `id` string, required — Unique person identifier with 'person_' prefix
    - `name` string, nullable — Optional name assigned to this person
    - `birth_date` string, date, nullable — Optional birth date of this person
    - `is_hidden` boolean, required — Whether this person should be hidden from the UI
    - `is_favorite` boolean, required — Whether this person is marked as a favorite
    - `asset_count` integer, nullable — Number of unique photos this person appears in, or null if not computed
    - `thumbnail_face_id` string, nullable — ID of the face resource used as this person's thumbnail
    - `asset_urls` object, nullable — Asset variants from this person's thumbnail face. May be null when embedded in an AssetResponse; use /api/people endpoints for full person data.
    - `cluster_metrics` ClusterMetricsResponse — Cohesion metrics for a Person's face cluster — surfaced via `include=cluster_metrics` on the people endpoints. These describe how tight the cluster is in embedding space (lower = more cohesive).
      - `pairwise_p90` number, required — 90th-percentile pairwise cosine distance between faces in this person's cluster. Lower = more cohesive cluster.
      - `pairwise_mean` number, required — Mean pairwise cosine distance between faces in this person's cluster.
      - `face_count` integer, required — Number of faces that fed into the centroid and pairwise metrics. This is the cluster-membership count, **not** the same as `asset_count` — `face_count` counts every face row, while `asset_count` counts distinct assets (one asset can contribute multiple faces of the same person).
    - `created_at` string, date-time, required — When this person record was created
    - `updated_at` string, date-time, required — When this person record was last updated
  - `kind` string, required — What produced the current rendering: `original` (the upload), `edit` (a client-baked edit), or `external:<service>`. The namespace is open — derive edited-ness as `kind != "original"`.
  - `current_version_id` string, required — ID (`asset_version_` prefix) of the current version, which the top-level rendering fields describe. Pass it as the expected current version on version writes so a racing write is rejected.
  - `width` integer — Width of the current rendering in pixels.
  - `height` integer — Height of the current rendering in pixels.
  - `duration` number, nullable — Video length in seconds. `null` for images and for videos whose duration has not been extracted yet.
  - `trashed_at` string, date-time, nullable — When this asset was moved to trash (ISO 8601, UTC). `null` for live assets. Trashed assets are excluded from default list/search results and are purged after the configured retention window.
  - `stack_id` string, nullable — ID of the stack this asset belongs to (`asset_stack_` prefix), or `null` when the asset is not part of a stack. Group assets by this value to collapse a stack into a single tile; the stack's own cover and member count are not carried on the asset. Distinct from `metadata.auto_stack_id`, which is the camera's in-EXIF `MakerNotes:AutoStackID` string — this is the server-assigned foreign key to the asset's stack.

## 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/revisions/f95c30b0b4e6/schema)
