// OPENBOX DOCS

API content and imports

Import, metadata, media, storefront, emulator, and audit routes.

Import sources, metadata and media management, emulators, storefronts, Gameyfin, and the library health audit. All routes require X-OpenBox-Token: TOKEN; POST bodies are JSON objects.

Folder and file imports

POST/api/import

Scan a folder and add supported files: {"folder": "/absolute/path", "chosen_emulators": {"Platform": "app_id"}}. Supported extensions include .sh, .appimage, .exe, .iso, .rom, console ROMs (.nes, .sfc, .smc, .gba, .gb, .gbc), and archives (.zip, .7z, .rar) plus .m3u, .cue, .chd, .wbfs, .rvz, .n64, .z64, .v64, .nds, .3ds, .cia, .pbp, .vpk, .xci, .nsp, .wad, .ciso, .gcm, .gcz, .wia, .dol, .elf, .xex. Multi-disc groups (Disc/disk/CD/DVD/side markers) are merged into an .m3u; duplicate ROMs are ranked and the best kept with version_candidates. Existing paths are never duplicated. When emulator choices are supplied, chosen emulators are installed from Flathub. Returns {"added", "found", "recommendations"}; media downloads for entries with a launchbox_db_id are queued respecting media_download_limit.

POST/api/import/wizard

Like /api/import but also installs each chosen emulator and returns {"added", "found", "recommendations", "installed": [app_ids]}.

POST/api/import/watch

Rescan all configured watch folders once. Returns {"added", "found", "errors": [...]}. The background auto-import worker rescans every 10 seconds (backing off to at most 300 on state errors) when the server runs.

POST/api/import/xbox360

{"folder", "command": ""} scans for default.xex, .xex, and .xbe; titles come from the folder name for default.xex. Returns {"added", "found"}.

POST/api/import/loose-arcade

{"folder", "command": ""} scans .zip, .7z, .singe, .rom files; defaults to hypseus/singe on PATH. Returns {"added", "found"}.

Storefront imports

POST/api/import/steam

Import installed Steam games from local manifests (steamapps/appmanifest_*.acf across all library folders). Missing launcher raises 400 (for example, "Steam, Flatpak, or xdg-open is required to launch imported Steam games."). Returns {"added", "found", "errors"}: unreadable libraries are reported, not skipped silently — each errors entry names the path and the OS error ("<steamapps path>: <strerror>", for example a read-only mount).

POST/api/import/heroic

Import installed Heroic games (legendary/gog/nile installed manifests). Missing launcher raises 400 ("xdg-open is required to launch imported Heroic games."). Returns {"added", "found"}.

POST/api/import/lutris

Import installed Lutris games (lutris --list-games --installed --json, or Flatpak) with a 30-second timeout. Missing launcher raises 400 ("Lutris or Flatpak is required to import Lutris games."). Returns {"added", "found"}. The health route POST /api/health/dedupe removes library duplicates by identity.

POST/api/import/arcade

{"folder", "dat": "", "command": "", "source": "MAME"} imports a MAME or FinalBurn Neo full set. With a DAT/XML file, entries are classified as parent, merged, split, or non-merged by comparing set ROMs with the ZIP contents. Without a DAT, MAME's own mame -listxml output is used (300-second timeout, 256 MiB cap) and mame must be installed. Missing folder or catalog raises 400. Returns {"added", "found", "sets": {"parent": n, "merged": n, "split": n, "non-merged": n}}.

POST/api/import/scummvm

Scan the ScummVM library (scummvm.ini sections). Returns {"added", "found"}.

POST/api/import/rpcs3

Scan the RPCS3 library (dev_hdd0/game/*/PARAM.SFO titles). Returns {"added", "found"}.

POST/api/import/vita3k

Scan the Vita3K library (ux0/app/*/sce_sys/param.sfo titles). Returns {"added", "found"}.

GET/api/faugus/status

Returns Faugus Launcher installation status and discovered data directories: {"installed": bool, "data_dirs": [str, ...]}.

GET/api/faugus/scan

Scans Faugus Launcher JSON manifests and prefix directories. Returns {"games": [...], "count": int}.

POST/api/faugus/import

Imports scanned Faugus games with UMU launch configurations and wine prefixes into the library. Returns {"added": int, "found": int, "imported": [str, ...], "count": int}.

GET/api/wine/prefixes

Discovers configured Proton and Wine prefix directories. Returns {"prefixes": [{"name", "path"}, ...], "available": bool}.

GET/api/wine/protons

Discovers Proton runtime executables (Steam compatibility tools, Faugus runners, Lutris runners, Flatpak/PATH). Returns {"protons": [{"name", "path"}, ...], "available": bool}.

GET/api/wine/prefix-for-game

Resolves the active Wine prefix directory for a given game (?id=<index> or ?game_id=<id>). Returns {"prefix": str, "available": bool}. Missing game returns 404.

Metadata

GET/api/metadata/status

{"ready": <database file exists>, "job": {...}, "coverage": {...}} where job is the in-memory metadata sync job (state downloading/done/error), and coverage reports library matching statistics (games, matched_games, matched_ratio, with_cover, etc.).

POST/api/metadata/sync

Start a background sync of the LaunchBox Games Database (https://gamesdb.launchbox-app.com/Metadata.zip, 2 GiB cap). Returns 202 {"state":"downloading"}; re-POSTing while downloading returns the current job with 200.

GET/api/metadata/search

?id=<index>&q=<title>&platform=<platform> searches the local database (20 results max). Before the database exists: 409 {"error":"Download the LaunchBox metadata database first."}. Results include database_id, name, platform, release_date, developer, publisher, genre, overview, series, esrb, max_players, cooperative.

POST/api/metadata/apply

{"id"|"game_id", "database_id": <int>, "media": ["cover"|"background"|"screenshots"], "overwrite": bool}. Applies text metadata and downloads the selected media into media/launchbox/<database_id>/. Missing database raises 409. Returns {"updated": [...], "notes": [...]} and bumps the media epoch.

POST/api/v1/metadata/match

POST /api/v1/metadata/match (aliased at POST /api/metadata/match) triggers a background job ("metadata-match") to batch auto-match library titles against the local LaunchBox database for all games currently missing launchbox_db_id. Missing database raises 409 Conflict.

Body: {"platform": "all" | "<platform-name>"}. Returns 202 {"state": "running"}.

POST/api/metadata/steam

Fetch Steam store metadata (appdetails) for a game with a numeric steam_app_id: name, developer, publisher, genre, year, description, cover, background. Missing App ID raises 400.

POST/api/metadata/trailer

Download the first Steam store trailer: requires steam_app_id. Returns {"video_trailer": "<path>"}.

POST/api/metadata/gog

Download GOG cover/background for a Heroic/GOG id. Returns {"cover", "background"}.

GET/api/metadata/igdb/search

?q=<title>&platform=<platform> searches IGDB (requires IGDB_CLIENT_ID/IGDB_CLIENT_SECRET in ~/.env; missing credentials raise 400). Results include id, name, summary, year, genres, platforms, rating, critic_score.

POST/api/metadata/igdb/apply

{"id"|"game_id", "igdb_id": <int>} applies name, description, year, genre, developer, publisher, rating, igdb_id, and time_to_beat_hours. Returns {"applied": true, "game": "<name>"}.

Automatic post-import scrape

The auto-scrape master toggle and its per-provider opt-ins are owned by the metadata routes, not the Settings dialog: they are stored in raw state settings so the feature never touches settings-handler normalization.

GET/api/v2/metadata/scrape-settings

Returns the four flags: scrape_after_import (master, default true) and scrape_screenscraper_enabled, scrape_igdb_enabled, scrape_steamgrid_enabled (all default false).

POST/api/v2/metadata/scrape-settings

Persists any subset of those flags. Values are coerced to booleans; unknown keys are ignored.

POST/api/v2/metadata/auto-scrape

Queues the match and media jobs for one import batch. Body: import_batch_id (required), optional media_types and overwrite. Returns 202 with match_job_id, media_job_id, and preview_id. When scrape_after_import is off the route answers 200 {"queued": false, "reason": "scrape_after_import is disabled"} instead of queueing. A missing batch id returns 400. (v1.14.0+)

GET/api/v2/metadata/media-candidates

Lists the media candidates discovered for a match preview, so the review UI can show what an apply would download before it downloads it.

SteamGridDB artwork hygiene

Batched artwork hygiene: a report pass finds wrong-size, duplicated, or mismatched covers, and fix/undo apply or roll back a batch.

GET/api/v2/steamgrid/hygiene/report

The current hygiene findings grouped by issue kind, with the affected games and the recommended action.

POST/api/v2/steamgrid/hygiene/fix

Applies a hygiene batch for the given findings. Requires the API key the other SteamGridDB artwork routes do.

POST/api/v2/steamgrid/hygiene/undo

Reverts the most recent hygiene batch and restores the previous artwork.

Media

GET/api/media

Serve one media file: ?id=<index>&kind=cover|background|clear_logo|fanart|banner|icon|box_back|box_spine|box_3d|title_screen|video|music|video_snap|video_theme|video_trailer|video_recording|screenshot&index=<n> (screenshot index for kind=screenshot). kind=video resolves the active video by priority. Missing file or bad kind: 404 {"error":"Media not found"}. Media responses carry immutable cache headers, ETag, and byte-range support.

POST/api/media/bulk

Start a background media download for every game with a launchbox_db_id: {"media": ["cover"|"background"|"screenshots"], "overwrite": bool, "platform": "<platform>|all"}. Returns 202 {"state":"running"}. Missing database raises 409.

GET/api/media/bulk/status

{"job": {"state": "running"|"done", "current", "total", "updated", "errors": [...]}} (last 20 errors).

GET/api/media/audit

?platform=<name or "all"> reports games, matched (has launchbox_db_id), missing_cover, missing_background, missing_screenshots.

GET/api/media/duplicates

{"groups": [{"keep": "<path>", "duplicates": [...]}]} by SHA-256 + size fingerprint of covers, backgrounds, and screenshots.

POST/api/media/cleanup

{"apply": bool, "platform": "<name>|all"} removes duplicate media. With apply: false it is a dry run returning candidate paths; with apply: true files are deleted only inside the data directory and the media epoch bumps. platform narrows the scan to one platform; blank or all scans the whole library. Returns {"groups", "paths": [...], "applied", "platform"}.

GET/api/media/queue

{"queue": [...]} from media-queue.json (pending media jobs).

POST/api/screenshot

Capture the screen with the first available tool among gnome-screenshot, spectacle, scrot, and ImageMagick import; appends to the game's screenshots and bumps the media epoch. Returns {"path"}.

Emulators

GET/api/emulators

{"emulators": [...], "install_all": {...}, "update_all": {...}}. Each emulator: app_id, name, platforms, installed, mode (native|flatpak|""), profiles (platform to command), can_install (flatpak present), recommendations. The dialog catalog covers Dolphin, PPSSPP, PCSX2, RPCS3, Cemu, MAME, xemu, ScummVM, RetroArch, DuckStation, melonDS, Eden, Vita3K, and Xenia, backed by the 24 adapter YAML files in emulator_defs/ (authoritative registry at GET /api/v2/emulators/registry).

GET/api/emulators/recommend

?platform=<name> returns {"recommendations": [...]} from the platform-to-emulator map.

GET/api/emulators/dependencies

?name=<emulator> returns {"required": [...], "missing": [...]} BIOS/system file checks (DuckStation, PCSX2, RPCS3, RetroArch hints).

POST/api/emulators/install

{"app_id": "org.mamedev.MAME"} installs from Flathub (adding the flathub remote if needed; 120 s remote, 1800 s install timeouts) and merges the platform profiles. Returns 202 {"state":"installing"}; duplicates return 200 with the current job. Errors surface as 400/500 with flatpak's stderr detail.

POST/api/emulators/install-all

Install every not-installed emulator. Returns 202 {"state":"installing"}; completion job has {"state":"done", "installed": [...], "errors": [...]}.

POST/api/emulators/update

{"app_id"} updates one emulator (Flatpak required; 400 "Flatpak is required for emulator updates."). Returns 202 {"state":"updating"}.

POST/api/emulators/update-all

Updates all installed flatpak-mode emulators. Returns 202 {"state":"updating"}.

POST/api/emulators/open

{"app_id"} launches the emulator standalone (native binary or flatpak run). Not installed: 400 "<name> is not installed.".

GET/api/emulators/definitions

{"definitions": [...]} YAML emulator definition packs (id, name, extensions, platforms, startup, executable_patterns, flatpak, native).

POST/api/emulators/scan

{"folder"} scans a folder using the definitions. Returns {"added", "found"}.

GET/api/emulators/scan-configs

Lists saved scan configurations (folder, emulator_id, auto_update). The auto-import worker executes configs with auto_update: true.

POST/api/emulators/scan-configs

Saves a scan configuration (folder, emulator_id, auto_update). Returns {"config": {...}}.

GET/api/v2/emulators/registry

Returns the emulator registry with platform coverage, adapter types, and installed status. Add ?health=1 to include per-adapter health status: bios_ok (BIOS file exists and SHA1 matches if expected), firmware_ok, and core_ok. The health check also reports BIOS_SHA1_DRIFT when a BIOS file exists but its hash doesn't match the expected value from the emulator definition. (v1.7.2+ for ?health=1)

GET/api/v2/emulators/defs/status

Reports the emulator-definition update channel: the installed community pack and its version, which definitions in the data directory are local edits, and whether the published index offers anything newer. A missing pack is a normal state, not an error — the status still answers.

GET/api/v2/emulators/defs/update

Checks the published index for a newer signed community pack and reports what an install would do, without writing anything.

POST/api/v2/emulators/defs/update

Installs the newest signed community definition pack. The archive is verified with the release Ed25519 key and validated in full before a single file is written; a signature failure raises a persisted security notification. The pack installs into the per-user data directory and shadows the bundled set without overwriting it, and a definition the user already has is kept. Retractions the pack declares are honored. (v1.15.0+)

POST/api/v2/emulators/defs/rollback

Removes exactly what the update channel installed and restores the bundled definition set. Never touches definitions the user created or edited.

Storefront catalog

GET/api/storefront/catalog

?source=steam|heroic|lutris|gameyfin returns {"catalog": [...]}. Steam reads installed manifests plus owned app ids from localconfig.vdf; Heroic reads library caches; Lutris uses --list-games --json; Gameyfin queries the configured server (requires gameyfin_url in settings, else 400). Entries carry id, name, source, installed, install_uri, path, launch, and store-specific ids. Provider errors return 400.

POST/api/storefront/import

{"source", "uninstalled_only": bool, "installed_only": bool} imports catalog entries as games (deduplicated by identity), marking store_catalog, store_installed, owned. Returns {"added", "found", "imported"}. Unknown source raises 400.

Gameyfin

GET/api/gameyfin/providers

{"providers": [...]} from the configured server. Connection failures return 400.

POST/api/gameyfin/test

Test connection using posted settings (merged over stored). Returns {"ok": true, "games": <n>, "providers": [...]}.

POST/api/gameyfin/install

{"gameyfin_id"|"id", "library_id": <optional>} downloads the game from Gameyfin into the install directory (staging with rollback; symlinks rejected; 4 GiB per file cap) and updates or appends the library entry. Returns 202 {"state":"installing", "gameyfin_id"}; without a gameyfin_id raises 400.

GET/api/gameyfin/install/status

?gameyfin_id=<id> returns the job: {"state": "idle"|"installing"|"done"|"error", "gameyfin_id", "error"?}. Missing id raises 400.

POST/api/gameyfin/uninstall

Removes the game's install directory and files (refusing symlinks and paths outside the install directory) and marks the entry uninstalled. Non-Gameyfin entries raise 400. Returns {"removed": [...], "game": {...}}.

Health

POST/api/health

Library audit: {"games", "missing", "duplicates", "unconfigured", "missing_media", "issues": [...]}. Issues are typed Duplicate, Missing game, Missing box front, Missing extra, Missing save path, and No emulator (ROM extensions without a launch command or platform profile).

POST/api/health/dedupe

Remove duplicate games by identity (steam app id, heroic, lutris, arcade rom_name, or normalized path). Returns {"removed": [names]}.

Discovery

GET/api/discovery

{"recently_added": [...], "never_played": [...], "short_sessions": [...], "highly_rated": [...], "continue_playing": [...], "random_picks": [...], "generated_at": ...} index lists (12 each).

Errors

Missing database prerequisites return 409 on metadata routes; missing local tools (mame, xdg-open, 7z, flatpak, lutris) return 400 with the exact requirement; provider failures return 400 with the provider's message. See REST API overview for the shared envelope.