# Immich v3 API Migration & Deploy Debugging **Date:** 2026-09-23 **Project:** frambe **Status:** ongoing **Tags:** immich-v3, api-migration, docker-compose, env-vars, deployment, search-metadata ## Context Immich released v3 (1 July 2026) with a large set of breaking API changes. Frambe called several endpoints that were removed, renamed, or reshaped, so albums/people/ random/favorites all needed updating. After shipping the code fixes (v1.6.0 on `dev`), the "Start slideshow" button flashed "no photos found" despite albums showing a nonzero count — which turned out to be a deploy/env problem, not a code problem. ## Key Decisions - Bumped **1.5.2 → 1.6.0** (minor). Rationale: substantial multi-endpoint compatibility fix pack for a breaking upstream change, but no new user-facing features. Not a major (no feature pack), not a patch (more than a small fix). - Album assets now fetched via **`POST /search/metadata` with `albumIds`**, because v3 removed `AlbumResponseDto.assets`. Kept a v2 fallback (`al.assets` if present) so the same code works against both server versions during transition. - Added a **`searchMetadata()` pagination helper** (pages via `assets.nextPage`, integer `page`/`size`) rather than one-shot single-page fetches — album/person sets can exceed one page of 250. - Person assets moved to `POST /search/metadata` with `personIds`, keeping the legacy `GET /people/:id/assets` as a try/catch fallback. Considered leaving the old endpoint alone (not listed as removed in the migration doc) but chose the canonical v3 path with fallback for durability. - **`visibility` handling is still open** — see Open Questions. Initially forced `visibility:'timeline'` everywhere to keep archived/hidden assets off frames; this is the leading suspect for empty album results and may need to be dropped for album/person queries. - Deploy stays **scp + ssh**, not the Gitea API: `server.js` is ~27KB, over the Gitea `update_file` ~16KB silent-truncation limit. Uploader BAT verifies md5 + `node --check` on the server before it will commit/push. ## Technical Details - Immich creds are set **inline in `docker-compose.yml`** under `environment:` (`IMMICH_URL`, `IMMICH_API_KEY`), NOT in `.env`. Compose ALSO references an `env_file:` (`.env`) that is gitignored and currently MISSING from `~/frambe` on hal-blackpearl — this makes every `docker compose` command abort before it does anything (rebuild, exec, up all fail with "env file ... not found"). - App reads `process.env.IMMICH_API_KEY` (server.js line 15) and `process.env.IMMICH_URL` (line 14). These match the compose `environment:` block. A debug test that printed `$API_KEY` showed empty — that was the WRONG var name in the test shell, a red herring, not an app bug. - Proven working in-container (with a valid key): `POST $IMMICH_URL/api/search/metadata` body `{"albumIds":[""],"size":5}` returns `assets.total:5` — so the v3 `albumIds` search logic is correct. The empty result in the UI is a runtime deploy/env issue, not the query. - Server on hal-blackpearl uses Alpine node image: has `wget`, not `curl`. Use `wget -qO-` for in-container HTTP tests. - Frambe app endpoint: `http://localhost:3030/api/albums/:id` (host port 3030). - v3 endpoint/shape changes applied: - `GET /albums` list: `?shared=true` → `?isShared=true`; added `?isOwned=true`. - `GET /albums/:id`: `AlbumResponseDto.assets` removed → search/metadata. - `GET /assets/random` REMOVED → `POST /search/random` (body `{size, visibility}`, returns a bare array; code tolerates array or `{assets:{items}}`). - `POST /search/metadata`: `page`/`size` now integers; omitted `visibility` now defaults to "any" (was "timeline"). - `AssetResponseDto.duration`: now a number in **milliseconds** (was "H:MM:SS.mmm" string) → `normDuration()` normalizes both to ms. - Error bodies moved to Zod shape + `X-Correlation-ID` header — NOT handled because Frambe only reads HTTP status codes, not error bodies. No change needed. ## Code Changes - `server.js` — VERSION → 1.6.0; added `normDuration()`, `searchMetadata()`; `mapAsset` uses `normDuration`; `/api/albums` uses isShared/isOwned; `/api/albums/:id` uses search/metadata (v2 fallback); `/api/people/:id` uses personIds search (legacy fallback); `/api/assets/random` uses search/random; `/api/assets/favorites` routed through `searchMetadata`. Added `logErr` to more catch blocks. - `package.json` — version 1.4.1 → 1.6.0 (was stale). - `public/js/app.js` — no changes (consumes the server's normalized shape). ## Open Questions / Next Steps - **Immediate blocker:** `.env` missing in `~/frambe` on hal-blackpearl, so Compose aborts. Fix by either (a) `cp .env.example .env` and fill real values (`TZ=Australia/Melbourne`, admin password, Frambe API token), or (b) drop the `env_file:` line from `docker-compose.yml` since the Immich creds already live in the `environment:` block. Recommendation leaning toward (b) — removes dependence on a gitignored file that keeps disappearing. - Once Compose runs again: `docker compose up -d --build --force-recreate`, then re-test `curl http://localhost:3030/api/albums/` — expected to return photos now that v1.6.0 code + valid key are both live. - **Decide on `visibility`:** if albums still come back empty after the container is confirmed current, drop `visibility:'timeline'` from the album/person searches (keep it only where wanted). Album assets aren't guaranteed timeline-visible, so forcing timeline can zero out results. Possibly make it a config toggle. - `docker-compose.yml` still has the obsolete top-level `version:` attribute → harmless warning; remove when convenient. - Not yet done: CHANGELOG entry / README version bump — deferred to the `main` merge. - Merge `dev` → `main` via Gitea UI once frames are confirmed working. ## Artifacts - `dev` HEAD: `55538e5` "v1.6 Updates for Immich V3 API Changes" - Immich v3 migration doc: https://immich.app (Migrating to v3, 1 July 2026) - Repo: https://gitea.hideawaygaming.com.au/jessikitty/frambe (branch: dev)