# Errors and refusals [← Documentation index](index.md) Every error the API returns carries a code: ```json {"error": {"code": "lock_held", "message": "a library_write job is already running"}} ``` Most of them are **refusals, not faults**. This application would rather stop and explain than guess about somebody's photographs, so a code below usually means it protected something. Each row says what caused it and what to do. ## Access and the trust boundary | code | cause | what to do | |---|---|---| | `unauthenticated` | no application session | reload the page; the browser bootstraps one | | `access_denied` | wrong or missing access secret | check `PHOTO_PIPELINE_ACCESS_SECRET`. Attempts are logged with the caller's address only | | `too_many_attempts` | more than five failed secret attempts in a minute | wait. This is the rate limit, not a lockout | | `csrf_failed` | a mutation without the session's token | reload; a stale tab has an old token | | `host_not_allowed` | the `Host` header is not a configured name | add the real hostname to `PHOTO_PIPELINE_ALLOWED_HOSTS` | | `origin_not_allowed` | the request came from another origin | not something a browser tab of this app produces | | `cross_site_blocked` | another site triggered the request | expected — this is the protection working | | `payload_too_large` | the body exceeds `PHOTO_PIPELINE_MAX_REQUEST_BYTES` | send less; every endpoint takes small commands | | `path_not_allowed` | a path resolved outside the library roots | usually a symlink. Nothing outside the roots is reachable, by design | ## The safety gate | code | cause | what to do | |---|---|---| | `dry_run_not_approved` | mutation is gated until a dry run is approved | run `dry-run`, read it, then `approve-dry-run` | | `approval_scope_mismatch` | the approval covers different library roots | approve a report for the roots actually configured | | `approval_unreadable` | the approval record cannot be read | re-approve; do not edit it by hand | ## Concurrency and staleness | code | cause | what to do | |---|---|---| | `lock_held` | a mutating job already holds the lane | wait for it. One at a time is what makes a crash recoverable | | `version_conflict` | you acted on a version that changed underneath you | the view re-renders with the server's truth; decide again | | `invalid_transition` | a state change that the machine does not allow | usually a stale tab; reload | | `job_error` | the job service refused the request | the message says why | | `rename_recovery_required` | an interrupted rename is unresolved | resolve it in [Renames](stages/renames.md). Unrelated mutations stay blocked on purpose | ## Stage refusals | code | cause | what to do | |---|---|---| | `nothing_to_score` | no photo is eligible for safety scoring | the queue is already decided | | `nothing_eligible` | no photo is eligible for analysis | resolve safety decisions first | | `nothing_to_plan` | no approved album name to build a plan from | approve a proposal first | | `invalid_decision` | the decision is not one this cluster accepts | reload the cluster | | `invalid_proposal` | the name is empty, reserved, or has forbidden characters | fix the name; the rules are in [Album proposals](stages/albums.md) | | `unknown_album` | the album is not a folder the library knows | rescan | | `cannot_apply` | the plan is invalid, stale, or another plan is unresolved | read the blockers in the preview | | `cannot_rollback` | the operation is complete or its preconditions no longer hold | rollback is recovery, not undo | ## Upload | code | cause | what to do | |---|---|---| | `stale_preflight` | bytes changed between approval and running | re-run preflight; this is the check that stops the wrong bytes being uploaded | | `not_runnable` | the batch is in a state that must not be re-run | an uncertain batch is verified, never retried | | `requires_verification` | the outcome is unknown; the server may hold the files | verify against Immich, then resolve | | `changed_after_upload` | the file changed after a successful upload | uploading again may create or upgrade an asset | ## Operations | code | cause | what to do | |---|---|---| | `backup_failed` | the snapshot could not be taken or verified | check free space and the message | | `invalid_retention` | a `--keep` value that is not a positive count | the newest backup is never pruned | | `not_found` | no such id | usually a stale link | ## Generic | code | cause | |---|---| | `invalid_request` | the request body failed validation. Field names only — never the values, which end up in logs and screenshots | | `http_error` | a plain HTTP-level refusal, with its status | | `internal_error` | an unhandled error. The code is all you get; the traceback is in the server log, because it can carry paths and credentials | ## Diagnostics warnings Not errors — reported by `diagnostics` and worth acting on. They are listed with their meanings in [Diagnostics](stages/diagnostics.md): `disk_low`, `disk_critical`, `cache_over_quota`, `wal_growth`, `tool_version_drift`, `legacy_process_active`, and `no_roots`. ## Startup refusals Exit codes 2 to 5 are covered by the installation manual under [when it refuses to start](installation.md#when-it-refuses-to-start).