Upload a new asset
Uploads a new asset (image or video) and its metadata as multipart form data, returning the created asset with 201. Uploads are deduplicated per library by the file’s SHA-256 checksum: re-uploading a file whose bytes already exist in the target library stores nothing and returns the existing asset with 200. Storage caps are checked before the duplicate lookup, so an upload is refused with 507 whenever the account or the target library is already at its storage cap — even when the bytes would have deduplicated to an existing asset. A transient upstream storage error returns 502 — retryable after the Retry-After interval. When library_id is omitted and no default library can be chosen (the account has multiple live libraries), the request is refused with 400. Image metadata is extracted before the response returns; the rest of processing (thumbnails, search indexing, face detection, and video metadata extraction) continues asynchronously after the response.
Authorizations
Gumnut API key (apikey_...) sent as a Bearer token in the Authorization header. Create and manage keys in the Gumnut app, or with the API key endpoints while signed in to it.
Body
The image or video file, sent as a binary multipart part with a filename. The file's MIME type is derived from the filename extension and must be an image or video type; files with an unrecognized or non-media extension are rejected with 422. The filename is stored as the asset's original file name (maximum 1024 characters). The API imposes no fixed per-file size limit; uploads are constrained only by the storage caps.
Identifier of this asset on the uploading device, chosen by the client (for example, the device's local asset ID). Stored verbatim and usable for device-based existence checks; plays no part in upload-time duplicate detection.
"IMG_0421"
Identifier of the uploading device or client, chosen by the client. Paired with device_asset_id for device-based existence checks.
"teds-iphone"
When the file was created on the uploading device, as an ISO 8601 datetime. Also serves as the fallback for the asset's local capture time when the file's embedded metadata carries no usable timestamp.
"2026-05-04T10:30:00+10:00"
When the file was last modified on the uploading device, as an ISO 8601 datetime.
"2026-05-04T10:30:00+10:00"
Library to upload into. For an all-library credential, omit to use the account's sole live library or create a fresh default when there are no live libraries; pass explicitly when the account has multiple live libraries. For a selected-library credential, omit to use its sole selected library; pass explicitly when it selects multiple libraries.
"lib_2c6ihMLYJUt3vctZWi9SNP"
Response
The file already exists in the target library (matched by SHA-256 checksum); the existing asset is returned and nothing new is stored.
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.
Unique asset identifier with 'asset_' prefix
MIME type of the current rendering (e.g., 'image/jpeg', 'video/mp4').
Filename the asset was uploaded under.
When the photo/video was taken, in the device's local timezone
When this asset record was created in the database
When this asset record was last updated
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".
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.
File/provenance scalars (device IDs, file timestamps, checksums, file size) grouped into one nested object. null when not requested via include=file_data; when present, every field is populated (checksum_sha1 may still be null for legacy rows).
Asset metadata — camera/EXIF fields, GPS, and location names. null when not requested via include=metadata.
ML-generated quality scores and other metrics. null when not requested via include=metrics.
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 (, 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.
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).
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.
All faces detected in this asset. null when not requested via include=faces; [] when requested but the asset has no faces.
All unique people identified in this asset (deduplicated from faces). null when not requested via include=people; [] when requested but none are identified.
Width of the current rendering in pixels.
Height of the current rendering in pixels.
Video length in seconds. null for images and for videos whose duration has not been extracted yet.
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.
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.