Skip to content

Automate Fotobank

Find photos, download originals, and manage the library from scripts or agents. The commands use the same server as the web app. In the default single-user setup, no owner-registration or login command is needed. See Set up Fotobank for that setup.

For a new deployment, follow setup for someone else first. This page is the detailed command reference, including partial results and retry behavior. Provider setup and consent are in the AI guide.

Set one configuration path for the whole operation instead of relying on the current working directory:

export FOTOBANK_CONFIG="$HOME/.config/fotobank/config.toml"
fotobank config validate

Commands that accept --config can use it instead; the explicit flag takes precedence over the environment variable. config path uses only the environment or default path; see separate configurations.

Prefer structured output

Use --json to read results without parsing progress messages:

fotobank config diagnose --json
fotobank media list --limit 20 --json
fotobank daemon status --json

Check the exit code as well as stdout. Commands return zero on success, one for runtime failures, and two for invalid command usage. Some commands report completed work even when later work fails; a JSON result alone is not proof that the whole operation succeeded.

Task Result and retry rules
Find or download photos Media commands and verified downloads
Import or recover interrupted work Import results
Edit tracked files Checkout results and conflicts
Create, inspect, or restore an archive Backup commands and required server mode
Manage albums or shares Album results and broker status
Queue AI work Completed task results and failures
Register owners Owner results

config diagnose --json writes an array of checks to stdout. Each check has name, status, and detail, plus action when there is a suggested next step. Statuses are ok, error, or disabled; disabled backups do not count as an error. Match checks by name rather than array position, and use the exit code: the command still emits its results when a check fails, then exits with one. A configuration that cannot be loaded or validated produces a single configuration error check. The failure summary goes to stderr.

Diagnostics inspect local configuration and storage without starting a daemon or initializing missing storage. They do not prove full catalog integrity or that a configured mount is healthy.

Import writes its final JSON result to stdout and live progress to stderr; interrupted connections and partial failures exit nonzero. Checkout list and status ask the daemon for saved catalog observations, not a fresh filesystem scan. They do not open or initialize a database in the CLI.

Know which process owns the vault

The daemon is Fotobank's background server. It owns the catalog, the Docbank vault, and application operations. Commands send requests to it. Some commands start it automatically; recovery requires an explicit choice:

Operation Server state
Content recovery Uses the daemon and starts it if needed; checks interrupted imports across all owners.
GPS backfill Uses the daemon and starts it if needed; header mode requires --owner or --all-owners.
Thumbnail regeneration Uses the daemon and starts it if needed; header mode requires --owner or --all-owners.
Owner registration, listing, or removal Uses the daemon's local operator API and starts it if needed; supports stub and header mode.
Hidden passcode setup, change, or disable Uses the daemon in stub mode; reads and validates passcodes and confirmation before starting it.
AI status or processing acknowledgment Uses the daemon in stub mode and starts it if needed; acknowledgment requires --hidden-processing.
AI backfill or retry-failed Uses the daemon in stub mode for tag, caption, and embed tasks; requires recorded processing acknowledgment.
Embedding-generation listing, promotion, or compaction Uses the daemon's local operator API in stub mode. Promotion asks for confirmation unless --yes; compaction supports --dry-run.
Admin hidden-passcode reset Uses the daemon's local operator API; requires --confirm and an explicit --owner in header mode.
Import, album and sharing commands, every checkout command, or manual backup create Uses the daemon in stub mode and starts it if needed; run under the same OS account and configuration.
Browse or use the HTTP API, scan checkout edits, scheduled backups Keep the server running.
Backup init, list with --repo, or verify Requires an already-running daemon in normal or recovery mode; never auto-starts. Requires configuration but no source storage in recovery mode.
Restore an archive to a separate target Requires an already-running recovery daemon; never auto-starts or changes modes. Requires saved configuration but no source storage.

Use fotobank daemon stop and fotobank daemon start for background operation. For a supervised service, use its supervisor; for a foreground fotobank serve, interrupt it and wait for shutdown to complete. Do not remove lock files or start a second process that opens the same vault. See the backup guide for recovery mode.

For tracked edits: keep the server running to create the checkout, edit and settle files, inspect checkout status --json, then explicitly commit with the server still running. See checkouts for the complete workflow.

Regenerate thumbnails

Use fotobank thumbs regenerate --all --json to queue new thumbnails for the configured stub owner. The daemon stays responsible for the catalog and workers; the command reports queued work, not finished images. Originals remain unchanged.

Select assets with repeatable --id, --type photo|video, --status, or --since 2026-01-01T00:00:00Z. Filters combine, including alongside --all. Only ready, non-hidden assets are eligible. Host operators may use --owner hub:user or --all-owners; header-mode deployments require one of these explicit scopes. These permissions belong to the local operator, not ordinary photo users.

The shared HTTP operation is POST /api/v1/operator/thumbs/regenerate. JSON returns items with hub, user_id, and enqueued for each processed owner. An error means the run stopped after any reported successes, and the CLI exits nonzero. Regeneration increments thumbnail versions: inspect media thumbnail status before retrying an interrupted request instead of blindly repeating it.

Find and inspect photos

Find photo IDs before adding them to albums or selecting a checkout:

fotobank media list --type photo --limit 20 --json
fotobank media list --camera "Example Camera" --lens "Wide" --has-gps --json
fotobank media search "sunset" --type photo --limit 20 --json
fotobank media show <media-uuid> --json
fotobank albums add <album-uuid> <media-uuid> --json

media list --json returns items and an optional next_offset. Continue with --offset and the same filters and sorting. Pages default to 100 items and accept 1–1,000. Sorting is by capture timestamp, oldest first; --sort-desc reverses it. Pages are live reads, not a snapshot across concurrent imports.

--type selects photo or video. Camera and lens values match exactly; repeat --camera, --lens, or --tag to match any value within that filter. Different filters combine. --tag takes a tag key, not a free-text search. Use --has-gps for geotagged photos or --has-gps=false for those without GPS.

media search [query] uses the same search as the web interface. Quote queries containing spaces. Omit the query to browse by filters. Metadata search works without AI; when embeddings are available, text queries also use semantic search. Search accepts the type, camera, lens, tag-key, and GPS filters above. Add --tag-label to require a tag label; repeated labels must all match. Use --location for an exact location label and RFC3339 timestamps with --date-after (inclusive) or --date-before (exclusive).

Search JSON returns results, has_more, and an optional next_cursor:

fotobank media search "sunset" --type photo --limit 20 --cursor <next-cursor> --json
fotobank media search --date-after 2026-01-01T00:00:00Z --sort newest --json

Keep the query, filters, and sort unchanged when continuing. Pages default to 60 results and accept 1–200. Sorting supports relevance, newest, and oldest; queries without text default to newest. Search pages are live reads, and text search covers a bounded candidate set, not an exhaustive catalog export. If the daemon rejects a cursor with HTTP 400, restart without --cursor. The CLI does not retry automatically. effective_sort, semantic_unavailable, and semantic_unavailable_reason describe how the returned page was searched. See search architecture for the candidate limit and cursor rules.

media show --json returns the photo's metadata and primary filename, size, and checksum. files contains attached files with their IDs, roles, sizes, and checksums; an ordinary single-file photo has an empty files array. Without --json, these commands print a readable summary. Failed requests leave stdout empty and exit nonzero.

These commands use the existing photo HTTP endpoints and start the daemon if needed. They use the configured stub owner and do not unlock hidden media or read other owners' photos.

Download an original or attachment

Save the primary file, or select an attachment ID from media show:

fotobank media download <photo-id> --output photo.jpg
fotobank media download <photo-id> --file <file-id> --output photo.xmp --json

--output is required. Relative paths are relative to the CLI's working directory, not the daemon's. The parent directory must already exist and support hardlinks. An existing file, directory, or symlink is never replaced. Binary stdout, bulk downloads, resume, and overwrite are not supported.

The CLI checks the destination before starting the daemon. It downloads to a temporary file in that directory, verifies the size and SHA-256 from the photo's metadata, then publishes the completed file. The hardlink is between two names of this new local copy, never a link to Docbank storage. The final name appears only after verification. Ctrl-C cancels the request. Failures before publication remove the temporary file during normal cleanup. An error during later cleanup or receipt output can leave the completed file; check the destination before retrying. Force-killing the process can leave a .fotobank-download-*.tmp file; remove it after confirming the command is no longer running.

JSON success contains media_id, optional file_id, the absolute output path, size, and sha256. A failed request or verification writes no success receipt and exits nonzero. If the stored content changes between reading its metadata and downloading it, verification can fail; retry the command. The CLI does not retry automatically or unlock hidden media. A downloaded file is an ordinary copy: editing it does not update Fotobank. Use a checkout when you want to commit edits as new versions.

Organize albums

Create albums and change their membership without copying or deleting photos. Commands use the configured stub owner and start the daemon if needed. Run them under the same OS account and configuration as the server:

fotobank albums create "Autumn walk" --json
fotobank albums add <album-uuid> <media-uuid> [<media-uuid>...] --json
fotobank albums list --json --limit 100 --offset 0
fotobank albums show <album-uuid> --sort-by taken --sort-asc --json
fotobank albums rename <album-uuid> "Autumn walks" --json
fotobank albums remove <album-uuid> <media-uuid>
fotobank albums delete <album-uuid>

list --json returns an object with items and, when more albums remain, next_offset. Pass that offset to the next request. List and member pages default to 100 rows and are capped at 1,000. show accepts --limit, --offset, and sorting by added, imported, or taken.

create --json and rename --json return the album object, including its id. add --json returns added and already_present counts, so adding the same photos again has an inspectable result. show --json returns album details and a media page with items and optional next_offset. Continue that page with --offset while keeping the same sort flags. The details and member page are separate daemon reads, not an atomic snapshot of a concurrently edited album.

These JSON commands write a result only after their requests succeed. On a request failure, stdout is empty, stderr explains the failure, and the exit code is nonzero. remove and delete still print human confirmations; neither API operation returns a result body. Use fotobank albums --help for examples and each subcommand's --help for its flags.

Adding existing members is harmless. Removing a member or deleting an album does not delete the photos; outstanding shares can block album deletion. Hidden photos cannot be added through these commands, and hidden members are excluded from the displayed member list. The CLI does not provide an unlock session or a header-mode user login. Other owners' albums remain inaccessible. After a connection failure during a change, inspect the album before retrying.

Manage sharing

Sharing needs more setup than a local single-user library. The default [broker].mode = "stub" records successful publication without contacting any external system. A share can therefore show broker_status = "active" without reaching a recipient. The default identity.mode = "stub" also supplies the same identity to every request; it does not let another person sign in.

For recipient access, configure the external identity proxy and broker integration described in identity and authorization and sharing architecture. [broker].mode = "exec" calls an operator-supplied broker executable; Fotobank does not supply that external service. In-app sharing controls are hidden by default; [ui].sharing_enabled = true shows them but does not configure identity or a broker. The CLI and API remain available when those controls are hidden.

Sharing commands also use the configured stub owner through the daemon:

fotobank shares create --album <album-uuid> --grantee hub:user
fotobank shares create --media <media-uuid>,<media-uuid> --grantee hub:user --allow-download
fotobank shares list --json --grantee hub:user --limit 100
fotobank shares show <share-uuid>
fotobank shares revoke <share-uuid>
fotobank shares list --status failed --json
fotobank shares retry <share-uuid>

Choose exactly one of --album or --media. Creation also accepts --label and an RFC3339 --expires time. Create, show, revoke, and retry return the same JSON objects as the HTTP API. list --json returns items and an optional next_offset; use --offset to continue. Lists default to 100 rows, cap at 500, and support album, grantee, and broker-status filters. Use --include-settled to include shares whose remote revocation is complete.

Creation and revocation queue work for the daemon's sharing worker. A successful command does not mean that broker work has finished; inspect broker_status with show or list. Retry applies only to failed shares. Retrying another state or revoking an already-revoked share returns a conflict and exits nonzero. Commands do not retry automatically after a lost response; inspect the share before repeating a change. These commands do not provide header-mode login.

Queue AI work and manage search generations

Queue missing AI work or retry recorded failures through the daemon in stub mode. Backfill finds work missing for the current AI settings; retry-failed selects recorded failures. Both require processing acknowledgment. Their counts describe queued jobs, not completed tagging, captions, or embeddings.

fotobank ai backfill --task tag,caption --json
fotobank ai retry-failed --task embed --json
fotobank ai list-generations --state retired
fotobank ai promote-generation <generation-id> --yes --json
fotobank ai compact-retired-generations --dry-run --json
fotobank ai compact-retired-generations --json

Backfill and retry JSON contains the daemon's enqueued count for each completed task, for example {"tag":{"enqueued":12},"caption":{"enqueued":8}}. Tasks run in the requested order and stop at the first failure. If a later task fails, stdout still contains completed task results; stderr explains the failure and the exit code is nonzero. Failed and unattempted tasks have no entry. If no task completed, stdout is empty. A failed task may have queued some jobs before failing; these responses cannot report that partial count. Inspect ai status before retrying after a request failure.

A search generation holds embeddings made with the same model and input settings. Promotion makes a retired generation active again; JSON returns its id and fingerprint. Without --yes, JSON mode puts the confirmation prompt on stderr. Declining leaves stdout empty and exits nonzero.

Compaction removes retired generations older than the configured retention window. Its JSON contains candidates, dropped, and an optional error. Dry-run lists candidates without deleting them. A failed sweep still reports how many generations it dropped, then exits nonzero; earlier deletions are not undone. A failed dry-run also returns the error as JSON. A failed HTTP request leaves stdout empty. Promotion and compaction do not require a working AI provider or processing acknowledgment.

Without --json, these four commands retain their readable output. ai status and ai list-generations always return JSON. See the AI architecture for generation ownership and processing rules.

Manage registered owners

This is advanced administration. The single-user setup registers your configured identity automatically; ordinary photo workflows do not need these commands.

An owner records whose photos, albums, and other catalog records these are. It is not a copyright claim or a person recognized in a photo. Fotobank identifies an owner with hub (the identity provider or namespace) and user_id (the user within it). handle is a display name. storage_key is a stable storage UUID, not a password or access token.

Registration creates that catalog record. It does not create a login, invite someone, or change the identity selected by the running daemon. In stub mode, configuration selects one identity for every request. In header mode, an external proxy supplies the authenticated identity. Owners share one embedded Docbank vault; Fotobank controls photo access, not separate Docbank accounts.

These commands belong to the host operator: the person administering the daemon and storage, rather than an ordinary photo user. Run them under the daemon's OS account with the same configuration, in either stub or header mode:

fotobank owners add --hub example --user-id user-a --handle "User A" --json
fotobank owners list --json
fotobank owners remove --hub example --user-id user-a

All three commands accept --config. Adding an owner generates a storage UUID unless you supply --storage-key. Repeating an add without a key keeps the existing key; specifying a different key for an existing owner returns a conflict. A non-empty --handle updates the display name.

add --json returns the registered owner with hub, user_id, storage_key, handle, and created_at. Repeating registration returns the existing storage key and creation time, with any requested display-name update. list --json returns these same records under items. Failed requests leave stdout empty, explain the failure on stderr, and exit nonzero. After a lost response, list owners before retrying; the registration may have succeeded.

The configured stub owner is registered automatically when the daemon starts; you do not need to add it first. To choose its storage UUID, set identity.stub.storage_key before the first start.

New registrations are usable without a restart. A storage UUID already assigned to another owner returns a conflict.

Removal unregisters an owner; it does not delete photos or working files. Owners referenced by assets, saved checkouts, albums, or shares cannot be removed, and --purge is not implemented. The active configured stub owner cannot be removed: change the identity configuration and restart first. After a lost response, list owners before repeating a change.

Before a command changes stored data

  • Validate configuration before imports or checkout writeback.
  • Confirm the source path printed at import startup and use config diagnose to inspect storage locations.
  • Run only one import or content-recovery operation at a time; the application lock rejects concurrent mutation.
  • Estimate a checkout before creating it and set --max-bytes for --all.
  • Review pending edits before checkout commit; it saves new stored versions.
  • Verify the archive before a recovery drill and restore into a separate empty directory. Starting the recovered deployment is a separate operator action.

For routine backups, initialize a repository and enable the server's archive schedule. Manual fotobank backup create --repo /backups/photos --json uses the running server's vault and captures all owners, including hidden media. After a lost connection, list and verify the repository before retrying. Manual archives remain outside scheduled retention; fotobank:scheduled is reserved for the server.

The HTTP API and CLI share application services, but not every command has a machine-readable form yet. An agent should fail on unexpected output rather than guessing from partially parsed text.

Discover JSON operations, including checkout and backup commands, search, AI, and filter counts, through /api/openapi.json on the running server or the repository's openapi.yaml. Interactive documentation is at /api/docs. A documented operation can still report that its service is unavailable; schema presence does not mean AI is enabled. Original downloads, thumbnails, and events also have OpenAPI entries, but return streams rather than JSON. Shared-media byte routes remain outside the generated contract. Consult the HTTP architecture and route implementations for those surfaces. Scripts should use supported commands and API operations rather than writing directly to the catalog or Docbank's storage.