// OPENBOX DOCS

API local administrator

Local administrator operations that are not a stable remote contract.

These routes manage the local installation and process. They are not a stable remote contract: treat them as local-administrator operations and verify behavior against the running version. All require X-OpenBox-Token: TOKEN; POST bodies are JSON objects.

Updates

GET/api/update

Check for a new release. Queries the GitHub releases API for vindeckyy/OpenBoxGL (30-second timeout) and returns:

{
  "current": "1.15.0",
  "latest": "1.15.0",
 "available": false,
 "last_checked": "<timestamp or empty>",
 "notes": "...",
 "appimage": "https://github.com/vindeckyy/OpenBoxGL/releases/download/.../OpenBox-x86_64.AppImage",
 "checksum": "sha256:...",
 "checksum_url": "...",
 "sig": true,
 "sig_url": "...",
 "page": "https://github.com/.../releases/tag/..."
}

Rules:

  • Pre-release and build-suffixed tags are never available.
  • The AppImage asset matching the running architecture (OpenBox-x86_64.AppImage or OpenBox-aarch64.AppImage) must come from the trusted https://github.com/vindeckyy/OpenBoxGL/releases/download/ prefix and a SHA-256 checksum must be present (asset digest or .sha256 file); otherwise 400 with a clear reason.
  • sig is a boolean indicating that a trusted .sig asset exists; sig_url contains its URL. A missing or untrusted signature makes an available update fail closed.
  • GitHub failures return 400 ("GitHub releases request failed (...)" / "Could not reach GitHub releases: ..."). Setting GITHUB_TOKEN (or GH_TOKEN) in ~/.env authenticates the request for a higher rate limit.
POST/api/update/install

Download and atomically install the verified update into the running AppImage. Prerequisites:

  • APPIMAGE must be set (the process must run from an AppImage), else 400 "Automatic updates require the OpenBox AppImage."
  • The update must be available and its URLs trusted.
  • The download streams with a 2 GiB cap and the SHA-256 is verified before the swap.
  • The current AppImage is renamed to <name>.previous<ext>; a failed swap restores it.

Returns {"installed": "<version>", "backup": "<path>"}. Restart to use the update.

On Windows there is no AppImage: the updater checks the same pinned key, SHA-256, and Ed25519 signature in pure Python (no OpenSSL), downloads OpenBox-<arch>-windows.zip, and swaps the installed tree, leaving the previous tree at <InstallDir>\share\openbox.previous for rollback. Windows installs land under %LOCALAPPDATA%\OpenBox by default.

Shutdown

POST/api/shutdown

{"force": bool} stops every running game (SIGTERM, or SIGKILL with force: true) and returns {"stopped": <count>, "forced": bool}. Games that already exited are skipped.

State recovery

POST/api/state/recover

Restore the library from library.json.bak or rolling snapshots (see Data and recovery).

  • Inspect: Body {"dry_run": true} returns available backups and snapshots: {"dry_run": true, "backup_available": bool, "games": int, "snapshots": [{"name", "size", "modified"}]}.
  • Restore backup: Body {} restores library.json.bak and returns {"ok": true, "games": int}.
  • Restore snapshot: Body {"snapshot": "<name>"} restores the specified snapshot. An unusable backup or unknown snapshot raises an error message.

Desktop integration

POST/api/desktop/install

Write the desktop entry and icon for the running AppImage: ~/.local/share/applications/io.openbox.GameLauncher.desktop and ~/.local/share/icons/hicolor/scalable/apps/io.openbox.GameLauncher.svg. Requires APPIMAGE (else 400). Returns {"desktop": "<path>"}.

Themes

GET/api/themes

?platform=<name> returns {"themes": [...], "selected": "...", "global": "...", "mappings": {...}}. Stock themes are installed on demand (Midnight Circuit, Phosphor Terminal, Harbor Light, Cinema Marquee, Nordic Mist). selected is the per-platform mapping when platform is given, else the global theme.

POST/api/themes/select

{"name": "theme-name"|"", "platform": ""} sets the global theme (or clears it with ""); with platform, sets/clears a per-platform mapping. Unknown theme file: 400. Returns {"selected", "platform"}.

POST/api/themes/import

{"path": "/path/to/theme.css"} copies a CSS file into themes/ (.css suffix required, max 256 KiB, must be valid UTF-8, symlinked sources/destinations rejected, remote @import with http rejected to respect style-src CSP). Returns {"theme": "<stem>"}. Errors: 400 with the exact reason when the file is missing, too large, a symlink, or contains a remote import.

POST/api/themes/open-folder

Open the themes folder in the file manager via xdg-open (missing xdg-open: 400). Returns {"path"}.

Plugins

GET/api/plugins

{"plugins": [{"id", "name", "version", "entry", "hooks", "enabled", "valid", "sandbox", ...}, ...]} sorted by id. Invalid packages are not hidden: they are reported with valid: false and a sandbox status so the manager can explain why they cannot run.

POST/api/plugins/install

{"path": "/path/to/plugin-dir-or.zip"} installs or updates a plugin package (safe extraction, staging, rollback on failure). Returns {"plugin": {...manifest..., "updated": bool}}.

POST/api/plugins/toggle

{"id", "enabled": bool} enables/disables a plugin (persisted in plugins-state.json). Returns {"enabled": bool}.

POST/api/plugins/remove

{"id"} moves the plugin directory to plugins/.removed/<id>-<timestamp> (reversible by moving it back) and clears its disabled state, trust grant, and permission grants. Returns {"removed": "<id>"}.

GET/api/plugins/catalog

{"catalog": [...]} from the commit-pinned remote catalog (20-second timeout, 4 MiB cap, and a pinned SHA-256 check on the response), falling back to the bundled local catalog.

POST/api/plugins/catalog/install

{"id"} downloads the catalog entry's package (128 MiB cap, SHA-256 verified when the entry provides one, 120-second timeout) and installs it. Unknown id or local_only entries raise 400 ("This catalog entry is documentation-only. Install local plugin packages manually."). Returns {"plugin": {...}}.

See Plugins for the manifest and hook contract.

Plugins 2.0 routes (1.14.0+)

GET/api/v2/plugins/catalog

The catalog enriched per entry with installed, installed_version, and update_available, plus a top-level sandbox field reporting the sandbox mode the host will use (ready, unavailable, or disabled).

GET/api/v2/plugins/commands

{api_version, sandbox, commands} — the manifest-declared commands from enabled, valid plugins, consumed by the command palette.

POST/api/v2/plugins/command

{plugin_id, command} runs that plugin's command hook in the sandbox and returns its result plus any notification. Unlike the chained hooks, a failure surfaces to the caller.

GET/api/v2/plugins/trust

?id=<plugin id> returns the per-plugin trust record: {trusted, checksum, checksum_matches, granted_at}. Trust is only reported true while the recorded checksum still matches the installed package.

POST/api/v2/plugins/trust

{id, trusted} grants or revokes unsandboxed-execution trust for one plugin, recording the package SHA-256 at grant time. There is no trust-everything switch.

POST/api/v2/plugins/permissions

{id, permissions} records a permission grant. Requested permissions must be in the whitelist (network in 1.14.0) and declared by the manifest; anything else returns 400.

GET/api/v2/plugins/settings

?id=<plugin id> returns {schema, values} for the Plugins manager form: the manifest's validated settings schema and the stored values merged over schema defaults.

POST/api/v2/plugins/settings

{id, values} validates and stores per-plugin settings against the manifest schema. An unknown key, an out-of-enum value, or a type/range violation returns 400.

Premium read models

GET/api/premium/strings

?locale=en|es|de|fr|pt returns {"locale", "strings": {...}}; unknown locales fall back to English.

GET/api/premium/media-packs

{"packs": [{"id", "name", "description", "kinds", "active"}, ...]}. Bundled packs: clear-logos-default, controller-xbox, controller-playstation, badges-core.

GET/api/premium/platform-categories

{"categories": {...}} platform-to-category mapping (Nintendo, Sony, Microsoft, Computer, Arcade, Adventure, ...) with user overrides merged.

POST/api/premium/media-packs/apply

{"id": "<pack id>"} activates a pack (and sets the controller prompt pack/hint for controller packs). Unknown pack: 400. Returns {"pack": {...}, "settings": {...}}.

Local reads

GET/api/log

{"log": "<last 250 KB of openbox.log>"}. The log redacts tokens, passwords, API keys, and authorization headers, but can contain game names and local file paths.

GET/api/theme.css

?name=<theme> serves the theme CSS with revalidation headers; unknown themes return an empty stylesheet.

GET/api/v1/diagnostic

GET /api/v1/diagnostic (aliased at GET /api/diagnostic) returns sanitized system diagnostic details generated by crash_report.py: host OS, Python runtime version, data directory, memory and process statistics, active settings summary, running session count, and library size. All secrets, tokens, passwords, and API keys are automatically redacted.

report is a JSON-encoded string (clients must JSON.parse it):

{
  "report": "{\"version\": \"1.15.0\", \"python\": \"3.12.3\", \"platform\": \"Linux-...\", \"data_dir\": \"~/.local/share/openbox-game-launcher\", \"games_count\": 42, \"running_count\": 0, \"settings_summary\": { ... }}"
}

The same report on Windows carries the Windows host and data directory:

{
  "report": "{\"version\": \"1.15.0\", \"python\": \"3.12.3\", \"platform\": \"Windows-...\", \"data_dir\": \"C:\\\\Users\\\\<you>\\\\AppData\\\\Local\\\\openbox-game-launcher\", \"games_count\": 42, \"running_count\": 0, \"settings_summary\": { ... }}"
}
GET/api/v1/jobs

GET /api/v1/jobs (aliased at GET /api/jobs) returns snapshots of all currently active background jobs (auto-import, metadata-match, media-bulk, emulator-install, etc.) and the 50 most recent completed or failed jobs with durations, attempt numbers, and error messages.

jobs is an object keyed by job name; history is an array:

{
  "jobs": {
    "metadata-match": {
      "name": "metadata-match",
      "job_id": "job-abc123",
      "state": "running",
      "started_at": "2026-08-17T12:00:00Z",
      "attempt": 1
    }
  },
  "history": [ ... ]
}
GET/api/native/capabilities

Returns native host capabilities detected by the backend when running inside the native wrapper (WebKitGTK on Linux, WebView2 on Windows), plus the identity fields Settings → About displays. Detection is the OPENBOX_NATIVE_HOST variable the host sets before spawning this server, so the payload shape is the same on both platforms; gamepad stays "webkit" either way, because the host's gamepad hook is a stub and the Web Gamepad API keeps serving.

{
  "webview": true,
  "dialogs": true,
  "tray": true,
  "single_instance": true,
  "gamepad": "webkit",
  "fullscreen": true,
  "clipboard": true,
  "version": "1.15.0",
  "platform": "win32",
  "data_dir": "C:\\Users\\<you>\\AppData\\Local\\openbox-game-launcher"
}

version, platform, and data_dir were added in v1.15.0. webview is what Settings → About reads to say whether you are in the native window or a browser tab — a browser tab is a supported configuration, not a failure state.

POST/api/native/dialog

Opens a native file or folder picker dialog on the host system. Body: {"kind": "folder" | "file" | "save"} (defaults to "folder"). Returns {"path": "/selected/path"} or {"path": null, "cancelled": true}. Unauthorized requests return 401 {"error": "unauthorized"}.

POST/api/native/reveal

Reveals a file or folder in the default desktop file manager (e.g. Nautilus, Dolphin). Body: {"path": "/path/to/game.rom"}. Returns {"ok": bool, "error": str | null}.

POST/api/native/open-external

Opens an external HTTP/HTTPS URL or file path in the host's default web browser or application. Body: {"url": "https://..."} or {"path": "/path/to/file"}. Returns {"ok": bool, "error": str | null}.

POST/api/native/window

Controls the native application window. Body: {"action": "minimize" | "toggle-maximize" | "close" | "set-fullscreen" | "unset-fullscreen"}. Returns {"ok": bool}.

GET/api/platform/documents

Retrieves the document metadata list for a platform (?platform=<name>) or mapping of all platforms when omitted. Returns {"documents": [{"name", "path"}, ...]}.

GET/api/platform/document

Serves a platform document file for inline viewing (?platform=<name>&index=<doc_index>). Symlinks are rejected. Missing document returns 404 {"error": "Platform document not found"}.

POST/api/platform/documents

{"platform", "documents": [{"name", "path"}, ...]} replaces the platform's document list (capped at 100 entries). Returns {"saved", "count"}.

GET/api/document

Serves a game's document file for inline viewing (?id=<index>&index=<doc_index> or ?game_id=<id>&index=<doc_index>). The file is validated to be a regular file (symlinks rejected), served with its guessed MIME type and a sanitized Content-Disposition name. Missing game, index, or file returns 404 {"error": "Document not found"}.

GET/api/explorer/facets

Facet counts for the Explorer rail (?field=genre|developer|publisher|platform|progress|esrb). Hidden games are skipped; missing values fall back to Unspecified / Unset / Unrated; results are sorted by count descending then name, capped at 40. Returns {"field", "facets": [{"value", "count"}, ...]}. Unknown fields return an empty list.

POST/api/bigbox/mode

Signals Big Box mode entry or exit. Body {"entering": true} runs the configured bigbox_shutdown_commands in new session groups (failures are logged, never block). Body {"entering": false} acknowledges mode exit. Returns {"ok": true, "entering": bool}.

Errors

These routes fail loudly on missing prerequisites (400 with the exact requirement) and never silently skip destructive steps. See REST API overview for the shared envelope.