// OPENBOX DOCS

API 1.12 additions

Request and response guidance for the Living Library v2 workflows shipped in OpenBox 1.12.0, and the 1.15.0 additions to them.

OpenBox 1.12.0 adds these authenticated, additive /api/v2/* workflows without changing the frozen v1 contract. The server still binds to loopback, chooses a random port at launch, and accepts X-OpenBox-Token: TOKEN on every protected route.

DATA_DIR="$HOME/.local/share/openbox-game-launcher"
TOKEN=$(cat "$DATA_DIR/server.token")
PORT=$(cat "$DATA_DIR/server.port")
BASE="http://127.0.0.1:$PORT"

Use the header form in scripts. The query-string token is accepted for the browser launch path but can leak into history and logs. POST bodies are JSON objects and are subject to the shared 65,536-byte body limit.

Smart collections

A smart collection stores a Backlog Radio query — not a snapshot of results — so membership is re-evaluated against the canonical search path on every read and can never drift from the interpretation chips shown when it was saved. Collections live in state["smart_collections"] as {name, query} pairs, at most 50 entries; names cap at 80 characters and queries at 500. An unparsable saved query (grammar drift) evaluates to zero matches, never an error.

MethodRoutePurpose
GET/api/v2/collectionsList saved collections with live match counts: {"items": [{name, query, count, ...}]}
POST/api/v2/collectionsCreate or replace a named collection. Body: {"name": "...", "query": "short unplayed rpg"}. Returns {"ok": true, "saved": "..."}. Invalid input returns 400 with code COLLECTION_INVALID.
POST/api/v2/collections/deleteRemove a collection by {"name": "..."}; games are never touched. Unknown names return 404 COLLECTION_NOT_FOUND.
curl -s -X POST -H "X-OpenBox-Token: $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "Short backlog", "query": "short unplayed rpg"}' \
  "$BASE/api/v2/collections"

curl -s -H "X-OpenBox-Token: $TOKEN" "$BASE/api/v2/collections"

The sidebar Collections section and the query bar's Save as collection chip use these routes; the grammar itself is the deterministic one documented in Backlog Radio and query grammar.

Game Story

GET /api/v2/story?game_id=<id> returns a pure read projection per game — nothing is written, so the timeline can never go stale. Events are derived from the game record, the session journal, and captured Moments: added, first_played, session (longest), milestone, progress, and moment, sorted chronologically.

curl -s -H "X-OpenBox-Token: $TOKEN" \
  "$BASE/api/v2/story?game_id=GAME_ID"

Response shape:

{
  "game_id": "game-...",
  "name": "...",
  "platform": "...",
  "events": [{"kind": "added", "at": "...", "title": "Added to library", ...}],
  "totals": {"sessions": 12, "playtime_seconds": 43120, "moments": 3, "progress": "..."}
}

A missing or unknown game returns 400 with code GAME_NOT_FOUND. The detail pane Story tab renders this payload; see Library overview.

Per-game launch environment and confirm

Two per-game fields complete the launch-options sheet (merge order stays game > platform > global):

FieldBehavior
launch_envKEY=value lines, validated at save time and merged over the spawn environment (after MangoHud) in pkg/state/launch.py. Invalid lines are rejected by the settings boundary.
launch_confirmBoolean; when set, the client asks for confirmation before launch preflight.

Both are edited from Edit game → Launch alongside the existing per-game launch command, profile, and gamescope preset overrides. They travel on the game record, so they are covered by the normal library read/write and export routes rather than a new endpoint family.

Scheduled automatic backups

Three settings drive the opt-in weekly library backup (auto_backup_due() in pkg/parity/parity_backup.py, run on an hourly daemon tick):

SettingDefaultMeaning
backup_auto_enabledfalseMaster switch for the weekly schedule
backup_auto_keep4Retention, 1–52 archives
last_auto_backup""Timestamp of the last automatic run; missing or unparsable counts as due

Content and rotation reuse create_backup unchanged, so the archives are identical to the manual ones documented in Library backups.

SQLite read model default threshold

GET /api/v2/library/search continues to honor OPENBOX_ENABLE_SQLITE_READ=1. Since 1.12.0 the FTS read model also self-enables at 5,000+ games (should_auto_enable(), latched per process); an explicit OPENBOX_ENABLE_SQLITE_READ=0/false/no opt-out is never overridden. The response source field reports sqlite or json, so clients can tell which path answered. JSON remains the source of truth either way.

1.15.0 additions

1.15.0 adds the emulator-definition update channel and a read-only Time Machine diff. Both are additive: the frozen v1 contract is untouched and neither introduces a new versioned namespace, so they stay /api/v2/* alongside the 1.12.0 surface above.

MethodRoutePurpose
GET/api/v2/emulators/defs/statusLocal definition state: installed pack version, which files the channel owns, which are user edits, and the bundled set.
GET/api/v2/emulators/defs/updateWhether the published index offers a newer pack, without fetching or writing it.
POST/api/v2/emulators/defs/updateFetch, verify, and install the signed community pack. A signature failure returns 400 {"error": "signature_verification_failed"} and raises a persisted security notification.
POST/api/v2/emulators/defs/rollbackRemove exactly what the channel installed, restoring the bundled set. User-authored definitions are never touched.
GET/api/v2/library/time-machine/compare?after=…&before=…Read-only diff of the library at two journal dates.

Full request and response detail lives on the group pages, where each route is described once: Content and imports for the definition channel, and API 1.11 additions for time-machine/compare. The table above is the index; it is deliberately not a second copy of those descriptions.