> ## Documentation Index
> Fetch the complete documentation index at: https://docs.gumnut.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# List assets in a library

> Returns a paginated list of assets ordered by local capture time (or trash time for trashed assets), newest first by default, optionally filtered by album, person, date range, geographic area, or asset ID. Use this tool for structured browsing and filtering — when the request can be expressed as exact filters on album membership, people, date range, geographic coordinates, or specific asset IDs.

**Location filtering is by coordinate:** pass a radius (`center` + `radius`) or a bounding box (`bbox`) to restrict results to a geographic area. The two modes are mutually exclusive. To count or cluster geotagged assets across a map viewport (how many photos fall in each area) rather than list them, use `get_geo_clusters`.

Album and person filters compose using AND; do not supply both `person_id` and `person_ids`.

**Use `search_assets` instead** when the request involves natural-language image content ('photos of sunsets', 'pictures with my dog'), a place *name* ('photos from Japan'), or any concept requiring semantic understanding of what's in the image. `list_assets` filters by coordinate but not by image content, place name, or caption text.

**To present a curated set of specific assets to the user** (e.g., a hand-picked subset of `search_assets` results), call this tool with `ids=[...]` rather than building a custom gallery — the asset IDs you already have are enough to re-render them through the interactive widget.

**Pagination** is cursor-based: while `has_more` is true, keep fetching with `starting_after_id`.



## OpenAPI

````yaml https://api.gumnut.ai/openapi.json get /api/assets
openapi: 3.1.0
info:
  title: Gumnut API
  description: API for using Gumnut to manage photos and videos
  version: 0.1.0
servers: []
security: []
paths:
  /api/assets:
    get:
      tags:
        - assets
      summary: List assets in a library
      description: >-
        Returns a paginated list of assets ordered by local capture time (or
        trash time for trashed assets), newest first by default, optionally
        filtered by album, person, date range, geographic area, or asset ID. Use
        this tool for structured browsing and filtering — when the request can
        be expressed as exact filters on album membership, people, date range,
        geographic coordinates, or specific asset IDs.


        **Location filtering is by coordinate:** pass a radius (`center` +
        `radius`) or a bounding box (`bbox`) to restrict results to a geographic
        area. The two modes are mutually exclusive. To count or cluster
        geotagged assets across a map viewport (how many photos fall in each
        area) rather than list them, use `get_geo_clusters`.


        Album and person filters compose using AND; do not supply both
        `person_id` and `person_ids`.


        **Use `search_assets` instead** when the request involves
        natural-language image content ('photos of sunsets', 'pictures with my
        dog'), a place *name* ('photos from Japan'), or any concept requiring
        semantic understanding of what's in the image. `list_assets` filters by
        coordinate but not by image content, place name, or caption text.


        **To present a curated set of specific assets to the user** (e.g., a
        hand-picked subset of `search_assets` results), call this tool with
        `ids=[...]` rather than building a custom gallery — the asset IDs you
        already have are enough to re-render them through the interactive
        widget.


        **Pagination** is cursor-based: while `has_more` is true, keep fetching
        with `starting_after_id`.
      operationId: list_assets
      parameters:
        - name: library_id
          in: query
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            description: >-
              Library to list assets from. Optional if the user has a single
              library; required when they have multiple.
            title: Library Id
          description: >-
            Library to list assets from. Optional if the user has a single
            library; required when they have multiple.
        - name: album_id
          in: query
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            description: >-
              Return only assets in this album — the album's `album_` ID, not
              its name. To browse one album's full asset metadata, prefer this
              filter over `list_album_assets`, which returns link records.
            title: Album Id
          description: >-
            Return only assets in this album — the album's `album_` ID, not its
            name. To browse one album's full asset metadata, prefer this filter
            over `list_album_assets`, which returns link records.
        - name: person_id
          in: query
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            description: Deprecated compatibility alias for a single `person_ids` value.
            deprecated: true
            title: Person Id
          description: Deprecated compatibility alias for a single `person_ids` value.
          deprecated: true
        - name: person_ids
          in: query
          required: false
          schema:
            anyOf:
              - items:
                  type: string
                type: array
              - type: 'null'
            description: >-
              Return only assets containing faces belonging to ALL of these
              people (intersection, not union). Accepts up to 200 IDs across
              repeated `person_ids=` query params or comma-delimited values.
              Person IDs are carried by the entries of an asset's `people` field
              (returned with `include=people`).
            title: Person Ids
          description: >-
            Return only assets containing faces belonging to ALL of these people
            (intersection, not union). Accepts up to 200 IDs across repeated
            `person_ids=` query params or comma-delimited values. Person IDs are
            carried by the entries of an asset's `people` field (returned with
            `include=people`).
        - name: stack_id
          in: query
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            description: >-
              Return only assets belonging to this stack (the `asset_stack_` ID
              carried by the `stack_id` field on every asset).
            title: Stack Id
          description: >-
            Return only assets belonging to this stack (the `asset_stack_` ID
            carried by the `stack_id` field on every asset).
        - name: ids
          in: query
          required: false
          schema:
            anyOf:
              - items:
                  type: string
                type: array
              - type: 'null'
            description: >-
              Look up specific assets by ID (max 200; each ID has the `asset_`
              prefix). Accepts multiple `ids=` query params or a single
              comma-delimited value (e.g., `ids=asset_1,asset_2`). Combines with
              other filters (album_id, person_ids, stack_id, datetime range)
              using AND logic — the result is the intersection.
            title: Ids
          description: >-
            Look up specific assets by ID (max 200; each ID has the `asset_`
            prefix). Accepts multiple `ids=` query params or a single
            comma-delimited value (e.g., `ids=asset_1,asset_2`). Combines with
            other filters (album_id, person_ids, stack_id, datetime range) using
            AND logic — the result is the intersection.
        - name: local_datetime_after
          in: query
          required: false
          schema:
            anyOf:
              - type: string
                format: date-time
              - type: 'null'
            title: Local Datetime After
            description: >-
              Only include assets captured strictly after this instant (ISO
              8601; exclusive). Convert a relative or natural-language date
              phrase ('in 2023') into an explicit bound before sending.
              `local_datetime` is the photo's wall-clock time in the device's
              own timezone. Naive values compare directly against
              `local_datetime`. Timezone-aware values: assets with a known
              offset are compared in UTC (`local_datetime - offset`); assets
              without an offset fall back to wall-clock comparison against
              `local_datetime`.
          description: >-
            Only include assets captured strictly after this instant (ISO 8601;
            exclusive). Convert a relative or natural-language date phrase ('in
            2023') into an explicit bound before sending. `local_datetime` is
            the photo's wall-clock time in the device's own timezone. Naive
            values compare directly against `local_datetime`. Timezone-aware
            values: assets with a known offset are compared in UTC
            (`local_datetime - offset`); assets without an offset fall back to
            wall-clock comparison against `local_datetime`.
        - name: local_datetime_before
          in: query
          required: false
          schema:
            anyOf:
              - type: string
                format: date-time
              - type: 'null'
            title: Local Datetime Before
            description: >-
              Only include assets captured strictly before this instant (ISO
              8601; exclusive). Same conversion requirement and awareness/offset
              semantics as `local_datetime_after`.
          description: >-
            Only include assets captured strictly before this instant (ISO 8601;
            exclusive). Same conversion requirement and awareness/offset
            semantics as `local_datetime_after`.
        - name: center
          in: query
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            description: >-
              Center point of a radius location filter: two comma-separated
              decimal-degree numbers `longitude,latitude`, e.g. `-77.05,38.95`.
              Supply with `radius`. Mutually exclusive with `bbox`.
            title: Center
          description: >-
            Center point of a radius location filter: two comma-separated
            decimal-degree numbers `longitude,latitude`, e.g. `-77.05,38.95`.
            Supply with `radius`. Mutually exclusive with `bbox`.
        - name: radius
          in: query
          required: false
          schema:
            anyOf:
              - type: number
              - type: 'null'
            description: >-
              Radius of the `center` location filter, in meters (greater than 0,
              at most 50000).
            title: Radius
          description: >-
            Radius of the `center` location filter, in meters (greater than 0,
            at most 50000).
        - name: bbox
          in: query
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            description: >-
              Bounding-box (map viewport) location filter: four comma-separated
              decimal-degree numbers
              `min_longitude,min_latitude,max_longitude,max_latitude`
              (west,south,east,north), e.g. `-77.1,38.9,-77.0,39.0`. A box whose
              `min_longitude` exceeds `max_longitude` crosses the antimeridian:
              it selects the band running east from `min_longitude` over ±180°
              to `max_longitude`, so there is no need to split it client-side.
              Longitude order is therefore significant — transposed corners read
              as a crossing viewport, not as an error. A viewport 360° or wider
              must be sent as the full range `-180,...,180,...`, which the
              wrapped form cannot express. Mutually exclusive with
              `center`/`radius`.
            title: Bbox
          description: >-
            Bounding-box (map viewport) location filter: four comma-separated
            decimal-degree numbers
            `min_longitude,min_latitude,max_longitude,max_latitude`
            (west,south,east,north), e.g. `-77.1,38.9,-77.0,39.0`. A box whose
            `min_longitude` exceeds `max_longitude` crosses the antimeridian: it
            selects the band running east from `min_longitude` over ±180° to
            `max_longitude`, so there is no need to split it client-side.
            Longitude order is therefore significant — transposed corners read
            as a crossing viewport, not as an error. A viewport 360° or wider
            must be sent as the full range `-180,...,180,...`, which the wrapped
            form cannot express. Mutually exclusive with `center`/`radius`.
        - name: starting_after_id
          in: query
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            description: >-
              Cursor for pagination. Pass the `id` of the last asset in the
              previous response's `data` to fetch the next page. Repeat the same
              filters, `state`, and `order` on every page. Omit for the first
              page. `list_assets` cursors because it walks a stable capture-time
              ordering; the sibling `search_assets` ranks by relevance and so
              pages by number instead.
            title: Starting After Id
          description: >-
            Cursor for pagination. Pass the `id` of the last asset in the
            previous response's `data` to fetch the next page. Repeat the same
            filters, `state`, and `order` on every page. Omit for the first
            page. `list_assets` cursors because it walks a stable capture-time
            ordering; the sibling `search_assets` ranks by relevance and so
            pages by number instead.
        - name: state
          in: query
          required: false
          schema:
            enum:
              - live
              - trashed
              - all
            type: string
            description: >-
              Which set of assets to read from: `live` (default — only assets
              that are not trashed), `trashed` (only trashed assets, ordered by
              trash time), or `all` (both live and trashed, ordered by capture
              time like `live`). Ordering defaults to newest or most recently
              trashed first.
            default: live
            title: State
          description: >-
            Which set of assets to read from: `live` (default — only assets that
            are not trashed), `trashed` (only trashed assets, ordered by trash
            time), or `all` (both live and trashed, ordered by capture time like
            `live`). Ordering defaults to newest or most recently trashed first.
        - name: order
          in: query
          required: false
          schema:
            enum:
              - asc
              - desc
            type: string
            description: >-
              Sort direction for the selected state's timestamp: capture time
              for `live`/`all`, or trash time for `trashed`. The asset ID
              tie-breaker uses the same direction.
            default: desc
            title: Order
          description: >-
            Sort direction for the selected state's timestamp: capture time for
            `live`/`all`, or trash time for `trashed`. The asset ID tie-breaker
            uses the same direction.
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            maximum: 200
            minimum: 1
            description: >-
              Maximum number of assets to return per page (1–200). Defaults to
              20.
            default: 20
            title: Limit
          description: Maximum number of assets to return per page (1–200). Defaults to 20.
        - name: include
          in: query
          required: false
          schema:
            anyOf:
              - items:
                  type: string
                type: array
              - type: 'null'
            description: >-
              Opt-in expansion fields. Supported values: `metadata`
              (camera/EXIF/GPS and location names), `faces`, `people`, `metrics`
              (ML quality scores), `file_data` (a group token populating the
              nested `file_data` object with the file/provenance scalars
              `device_asset_id`, `device_id`, `file_created_at`,
              `file_modified_at`, `checksum`, `checksum_sha1`,
              `file_size_bytes`), and `variants` (the non-thumbnail `asset_urls`
              size variants; without it `asset_urls` carries only its lean rung
              — `thumbnail`, or `thumbnail_image` for a video with an extracted
              still, or `original` for a still-less video — so callers that
              render non-thumbnail variants must pass it). Accepts multiple
              `include=` query params or a single comma-delimited value (e.g.
              `include=faces,people`). Unknown values return 422. When omitted,
              only the lean core is returned (`id`, `mime_type`,
              `local_datetime`, dimensions, `description`, `thumbhash`,
              `asset_urls`) and each data field above is null/absent until you
              request it.
            title: Include
          description: >-
            Opt-in expansion fields. Supported values: `metadata`
            (camera/EXIF/GPS and location names), `faces`, `people`, `metrics`
            (ML quality scores), `file_data` (a group token populating the
            nested `file_data` object with the file/provenance scalars
            `device_asset_id`, `device_id`, `file_created_at`,
            `file_modified_at`, `checksum`, `checksum_sha1`, `file_size_bytes`),
            and `variants` (the non-thumbnail `asset_urls` size variants;
            without it `asset_urls` carries only its lean rung — `thumbnail`, or
            `thumbnail_image` for a video with an extracted still, or `original`
            for a still-less video — so callers that render non-thumbnail
            variants must pass it). Accepts multiple `include=` query params or
            a single comma-delimited value (e.g. `include=faces,people`).
            Unknown values return 422. When omitted, only the lean core is
            returned (`id`, `mime_type`, `local_datetime`, dimensions,
            `description`, `thumbhash`, `asset_urls`) and each data field above
            is null/absent until you request it.
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PaginatedAssetsResponse'
        '404':
          description: Not found
        '422':
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HTTPValidationError'
components:
  schemas:
    PaginatedAssetsResponse:
      properties:
        data:
          items:
            $ref: '#/components/schemas/AssetResponse'
          type: array
          title: Data
          description: List of assets
        has_more:
          type: boolean
          title: Has More
          description: >-
            True if there are more assets after this page. Pass the last asset's
            `id` as `starting_after_id` and repeat the same filters, `state`,
            and `order` to fetch the next page.
      type: object
      required:
        - data
        - has_more
      title: PaginatedAssetsResponse
    HTTPValidationError:
      properties:
        detail:
          items:
            $ref: '#/components/schemas/ValidationError'
          type: array
          title: Detail
      type: object
      title: HTTPValidationError
    AssetResponse:
      properties:
        id:
          type: string
          title: Id
          description: Unique asset identifier with 'asset_' prefix
        mime_type:
          type: string
          title: Mime Type
          description: MIME type of the file (e.g., 'image/jpeg', 'video/mp4')
        original_file_name:
          type: string
          title: Original File Name
          description: Original filename when the asset was uploaded
        local_datetime:
          type: string
          format: date-time
          title: Local Datetime
          description: When the photo/video was taken, in the device's local timezone
        file_data:
          anyOf:
            - $ref: '#/components/schemas/FileDataResponse'
            - type: 'null'
          description: >-
            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).
        created_at:
          type: string
          format: date-time
          title: Created At
          description: When this asset record was created in the database
        updated_at:
          type: string
          format: date-time
          title: Updated At
          description: When this asset record was last updated
        metadata:
          anyOf:
            - $ref: '#/components/schemas/MetadataResponse'
            - type: 'null'
          description: >-
            Asset metadata — camera/EXIF fields, GPS, and location names. `null`
            when not requested via `include=metadata`.
        metrics:
          anyOf:
            - additionalProperties:
                anyOf:
                  - type: number
                  - type: 'null'
              type: object
            - type: 'null'
          title: Metrics
          description: >-
            ML-generated quality scores and other metrics. `null` when not
            requested via `include=metrics`.
        asset_urls:
          anyOf:
            - additionalProperties:
                $ref: '#/components/schemas/AssetVariant'
              type: object
            - type: 'null'
          title: Asset Urls
          description: >-
            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:
          anyOf:
            - type: string
            - type: 'null'
          title: Description
          description: >-
            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:
          anyOf:
            - type: string
            - type: 'null'
          title: Thumbhash
          description: >-
            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:
          anyOf:
            - items:
                $ref: '#/components/schemas/FaceResponse'
              type: array
            - type: 'null'
          title: Faces
          description: >-
            All faces detected in this asset. `null` when not requested via
            `include=faces`; `[]` when requested but the asset has no faces.
        people:
          anyOf:
            - items:
                $ref: '#/components/schemas/PersonResponse'
              type: array
            - type: 'null'
          title: People
          description: >-
            All unique people identified in this asset (deduplicated from
            faces). `null` when not requested via `include=people`; `[]` when
            requested but none are identified.
        width:
          type: integer
          title: Width
          description: Width of the asset in pixels
          default: 0
        height:
          type: integer
          title: Height
          description: Height of the asset in pixels
          default: 0
        duration:
          anyOf:
            - type: number
            - type: 'null'
          title: Duration
          description: >-
            Video length in seconds. `null` for images and for videos whose
            duration has not been extracted yet.
        trashed_at:
          anyOf:
            - type: string
              format: date-time
            - type: 'null'
          title: Trashed At
          description: >-
            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:
          anyOf:
            - type: string
            - type: 'null'
          title: Stack Id
          description: >-
            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.
      type: object
      required:
        - id
        - mime_type
        - original_file_name
        - local_datetime
        - created_at
        - updated_at
      title: AssetResponse
      description: Represents a photo or video asset with metadata and access URLs.
    ValidationError:
      properties:
        loc:
          items:
            anyOf:
              - type: string
              - type: integer
          type: array
          title: Location
        msg:
          type: string
          title: Message
        type:
          type: string
          title: Error Type
        input:
          title: Input
        ctx:
          type: object
          title: Context
      type: object
      required:
        - loc
        - msg
        - type
      title: ValidationError
    FileDataResponse:
      properties:
        device_asset_id:
          type: string
          title: Device Asset Id
          description: Original asset identifier from the device that uploaded this asset.
        device_id:
          type: string
          title: Device Id
          description: Identifier of the device that uploaded this asset.
        file_created_at:
          type: string
          format: date-time
          title: File Created At
          description: When the file was created on the uploading device.
        file_modified_at:
          type: string
          format: date-time
          title: File Modified At
          description: When the file was last modified on the uploading device.
        checksum:
          type: string
          title: Checksum
          description: >-
            Base64-encoded SHA-256 hash of the asset contents for duplicate
            detection and integrity.
        checksum_sha1:
          anyOf:
            - type: string
            - type: 'null'
          title: Checksum Sha1
          description: >-
            Base64-encoded SHA-1 hash for Immich client compatibility. `null`
            for older assets that have no SHA-1.
        file_size_bytes:
          type: integer
          title: File Size Bytes
          description: File size of the asset in bytes.
      type: object
      required:
        - device_asset_id
        - device_id
        - file_created_at
        - file_modified_at
        - checksum
        - file_size_bytes
      title: FileDataResponse
      description: >-
        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.
    MetadataResponse:
      properties:
        asset_id:
          type: string
          title: Asset Id
          description: ID of the asset this metadata belongs to
        created_at:
          type: string
          format: date-time
          title: Created At
          description: When this metadata record was created
        updated_at:
          type: string
          format: date-time
          title: Updated At
          description: When this metadata record was last updated
        make:
          anyOf:
            - type: string
            - type: 'null'
          title: Make
          description: Camera manufacturer (e.g., 'Canon', 'Nikon')
        model:
          anyOf:
            - type: string
            - type: 'null'
          title: Model
          description: Camera model (e.g., 'EOS 5D Mark IV')
        orientation:
          anyOf:
            - type: integer
            - type: 'null'
          title: Orientation
          description: >-
            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:
          anyOf:
            - type: integer
            - type: 'null'
          title: Raw Width
          description: Pre-rotation raw width; null when not available
        raw_height:
          anyOf:
            - type: integer
            - type: 'null'
          title: Raw Height
          description: Pre-rotation raw height; null when not available
        modified_datetime:
          anyOf:
            - type: string
              format: date-time
            - type: 'null'
          title: Modified Datetime
          description: When the file was last modified, with timezone offset if available
        original_datetime:
          anyOf:
            - type: string
              format: date-time
            - type: 'null'
          title: Original Datetime
          description: >-
            When the photo was originally taken, with timezone offset if
            available
        digitized_datetime:
          anyOf:
            - type: string
              format: date-time
            - type: 'null'
          title: Digitized Datetime
          description: When the photo was digitized, with timezone offset if available
        lens_model:
          anyOf:
            - type: string
            - type: 'null'
          title: Lens Model
          description: Lens model used (e.g., 'EF 24-70mm f/2.8L II USM')
        f_number:
          anyOf:
            - type: number
            - type: 'null'
          title: F Number
          description: Aperture f-stop value (e.g., 2.8, 5.6)
        focal_length:
          anyOf:
            - type: number
            - type: 'null'
          title: Focal Length
          description: Focal length in millimeters
        iso:
          anyOf:
            - type: integer
            - type: 'null'
          title: Iso
          description: ISO sensitivity value (e.g., 100, 800, 3200)
        exposure_time:
          anyOf:
            - type: number
            - type: 'null'
          title: Exposure Time
          description: Shutter speed in seconds (e.g., 0.001 for 1/1000s)
        exposure_bias:
          anyOf:
            - type: number
            - type: 'null'
          title: Exposure Bias
          description: Exposure compensation in EV (e.g., -1.0, +0.5)
        latitude:
          anyOf:
            - type: number
            - type: 'null'
          title: Latitude
          description: GPS latitude in decimal degrees
        longitude:
          anyOf:
            - type: number
            - type: 'null'
          title: Longitude
          description: GPS longitude in decimal degrees
        altitude:
          anyOf:
            - type: number
            - type: 'null'
          title: Altitude
          description: GPS altitude in meters
        city:
          anyOf:
            - type: string
            - type: 'null'
          title: City
          description: City name
        state:
          anyOf:
            - type: string
            - type: 'null'
          title: State
          description: State/province name
        country:
          anyOf:
            - type: string
            - type: 'null'
          title: Country
          description: Country name
        country_code:
          anyOf:
            - type: string
            - type: 'null'
          title: Country Code
          description: ISO 3166-1 alpha-2 country code (e.g., 'US', 'JP')
        sublocation:
          anyOf:
            - type: string
            - type: 'null'
          title: Sublocation
          description: Neighborhood or district
        place_name:
          anyOf:
            - type: string
            - type: 'null'
          title: Place Name
          description: Landmark or point-of-interest name
        timezone:
          anyOf:
            - type: string
            - type: 'null'
          title: Timezone
          description: IANA timezone identifier (e.g., 'America/Los_Angeles')
        display_label:
          anyOf:
            - type: string
            - type: 'null'
          title: Display Label
          description: >-
            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:
          anyOf:
            - type: string
            - type: 'null'
          title: Description
          description: Image description or caption
        fps:
          anyOf:
            - type: number
            - type: 'null'
          title: Fps
          description: Frame rate for video files
        live_photo_cid:
          anyOf:
            - type: string
            - type: 'null'
          title: Live Photo Cid
          description: Live photo content identifier
        projection_type:
          anyOf:
            - type: string
            - type: 'null'
          title: Projection Type
          description: Projection type (e.g., for 360° photos)
        auto_stack_id:
          anyOf:
            - type: string
            - type: 'null'
          title: Auto Stack Id
          description: Identifier for automatic photo stacking
        rating:
          anyOf:
            - type: integer
            - type: 'null'
          title: Rating
          description: User or camera rating (typically 1-5 stars)
      type: object
      required:
        - asset_id
        - created_at
        - updated_at
      title: MetadataResponse
      description: Metadata for an asset — camera/EXIF fields, GPS, and location names.
    AssetVariant:
      properties:
        url:
          type: string
          title: Url
          description: URL to fetch this image variant
        mimetype:
          type: string
          title: Mimetype
          description: MIME type of the served image
        width:
          anyOf:
            - type: integer
            - type: 'null'
          title: Width
          description: Target width in pixels (null if unknown)
      type: object
      required:
        - url
        - mimetype
      title: AssetVariant
      description: A single image variant with its URL, MIME type, and target width.
    FaceResponse:
      properties:
        id:
          type: string
          title: Id
          description: Unique face identifier with 'face_' prefix
        asset_id:
          type: string
          title: Asset Id
          description: ID of the asset containing this face
        person_id:
          anyOf:
            - type: string
            - type: 'null'
          title: Person Id
          description: ID of the person this face belongs to (if identified)
        bounding_box:
          additionalProperties:
            type: integer
          type: object
          title: Bounding Box
          description: Face location as {x, y, w, h} coordinates in pixels
        confidence:
          anyOf:
            - type: number
            - type: 'null'
          title: Confidence
          description: >-
            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:
          type: string
          enum:
            - automatic
            - manual
          title: Source
          description: >-
            How this face was added: 'automatic' for detector-found faces,
            'manual' for user-drawn face boxes.
        timestamp_ms:
          anyOf:
            - type: integer
            - type: 'null'
          title: Timestamp Ms
          description: For video files, timestamp in milliseconds when face appears
        asset_urls:
          anyOf:
            - additionalProperties:
                $ref: '#/components/schemas/AssetVariant'
              type: object
            - type: 'null'
          title: Asset Urls
          description: 'Asset variants for this face: ''thumbnail'' with face crop'
        created_at:
          type: string
          format: date-time
          title: Created At
          description: When this face was detected and recorded
        updated_at:
          type: string
          format: date-time
          title: Updated At
          description: When this face record was last updated
        cluster_assignment:
          anyOf:
            - $ref: '#/components/schemas/ClusterAssignmentResponse'
            - type: 'null'
          description: >-
            Cluster-assignment diagnostics for this face. Populated only when
            `include=cluster_assignment` is requested on the faces endpoint;
            null otherwise. See `ClusterAssignmentResponse` for the shape.
      type: object
      required:
        - id
        - asset_id
        - bounding_box
        - source
        - created_at
        - updated_at
      title: FaceResponse
      description: Represents a detected face in an asset with facial recognition data.
    PersonResponse:
      properties:
        id:
          type: string
          title: Id
          description: Unique person identifier with 'person_' prefix
        name:
          anyOf:
            - type: string
            - type: 'null'
          title: Name
          description: Optional name assigned to this person
        birth_date:
          anyOf:
            - type: string
              format: date
            - type: 'null'
          title: Birth Date
          description: Optional birth date of this person
        is_hidden:
          type: boolean
          title: Is Hidden
          description: Whether this person should be hidden from the UI
        is_favorite:
          type: boolean
          title: Is Favorite
          description: Whether this person is marked as a favorite
        asset_count:
          anyOf:
            - type: integer
            - type: 'null'
          title: Asset Count
          description: >-
            Number of unique photos this person appears in, or null if not
            computed
        thumbnail_face_id:
          anyOf:
            - type: string
            - type: 'null'
          title: Thumbnail Face Id
          description: ID of the face resource used as this person's thumbnail
        asset_urls:
          anyOf:
            - additionalProperties:
                $ref: '#/components/schemas/AssetVariant'
              type: object
            - type: 'null'
          title: Asset Urls
          description: >-
            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:
          anyOf:
            - $ref: '#/components/schemas/ClusterMetricsResponse'
            - type: 'null'
          description: >-
            Cohesion metrics for this person's face cluster. Populated only when
            `include=cluster_metrics` is requested on the people endpoint, and
            only for persons with a populated centroid (newly-created empty
            Persons will have null). See `ClusterMetricsResponse` for the shape.
        created_at:
          type: string
          format: date-time
          title: Created At
          description: When this person record was created
        updated_at:
          type: string
          format: date-time
          title: Updated At
          description: When this person record was last updated
      type: object
      required:
        - id
        - is_hidden
        - is_favorite
        - created_at
        - updated_at
      title: PersonResponse
      description: Represents a person identified through face clustering and recognition.
    ClusterAssignmentResponse:
      properties:
        distance_to_person:
          anyOf:
            - type: number
            - type: 'null'
          title: Distance To Person
          description: >-
            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:
          items:
            $ref: '#/components/schemas/FaceCandidatePersonResponse'
          type: array
          title: Candidates
          description: >-
            Persons in the same library that pass the same gate shape as
            production face assignment, surfaced with deliberately relaxed
            thresholds so the list is a superset of what the automated path
            would admit. Sorted ascending by distance. Excludes the face's
            currently-assigned Person (its distance is in `distance_to_person`).
            Empty when no eligible Persons pass the gate.
      type: object
      title: ClusterAssignmentResponse
      description: |-
        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 — used by the operator-facing face cleanup dashboard
        to triage mis-clustered faces.
    ClusterMetricsResponse:
      properties:
        pairwise_p90:
          type: number
          title: Pairwise P90
          description: >-
            90th-percentile pairwise cosine distance between faces in this
            person's cluster. Lower = more cohesive cluster; loose clusters
            (higher pairwise_p90) are gated out of the face-assignment path to
            prevent further drift.
        pairwise_mean:
          type: number
          title: Pairwise Mean
          description: >-
            Mean pairwise cosine distance between faces in this person's
            cluster.
        face_count:
          type: integer
          title: Face Count
          description: >-
            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).
      type: object
      required:
        - pairwise_p90
        - pairwise_mean
        - face_count
      title: ClusterMetricsResponse
      description: |-
        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) and
        drive both the production face-assignment cohesion gate and the
        operator-facing face cleanup dashboard.
    FaceCandidatePersonResponse:
      properties:
        person_id:
          type: string
          title: Person Id
          description: Person ID (with 'person_' prefix) of the candidate.
        name:
          anyOf:
            - type: string
            - type: 'null'
          title: Name
          description: >-
            Display name of the candidate Person, or null for unnamed clusters.
            Candidates surface the same Persons production assignment considers,
            which includes unnamed clusters.
        distance:
          type: number
          title: Distance
          description: >-
            Cosine distance from the face's embedding to this Person's centroid
            (lower = closer).
      type: object
      required:
        - person_id
        - distance
      title: FaceCandidatePersonResponse
      description: |-
        A Person whose centroid is close enough to a given face's embedding
        that it would be considered for assignment — surfaced under
        ``ClusterAssignmentResponse.candidates``.

````