// OPENBOX DOCS
API 1.11 additions
Request and response guidance for the local-first v2 workflows shipped in OpenBox 1.11.0.
OpenBox 1.11.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. Durable operations return 202 with a job_id; inspect them through the jobs endpoints documented in Saves and operations. On Windows the data directory is %LOCALAPPDATA%\openbox-game-launcher, so server.token and server.port live there instead.
Quick Resume and Moments
Quick Resume reports adapter capability and state freshness before loading a saved emulator state. Pass allow_stale: true only when you have reviewed the adapter or emulator-version mismatch.
| Method | Route | Purpose |
|---|---|---|
GET | /api/v2/resume/status?game_id=GAME_ID | Report whether the game is enabled, capable, available, or stale. |
POST | /api/v2/resume | Resume a game from its captured state. Body: game_id, optional allow_stale. |
POST | /api/v2/resume/discard | Delete the captured state and metadata. Body: game_id. |
Moments are bounded timeline records attached to one game. A capture can include a screenshot when the host supports it, but the note and trigger remain durable when capture is unavailable.
| Method | Route | Purpose |
|---|---|---|
GET | /api/v2/moments?game_id=GAME_ID&limit=100 | List a game's newest moments. limit is 1–500. |
GET | /api/v2/moments?moment_id=MOMENT_ID | Fetch one moment and its game id. |
POST | /api/v2/moments | Create a moment. Body: game_id, optional title, note, and trigger. |
POST | /api/v2/moments/resume | Launch the exact state linked to a moment. Body: moment_id, optional game_id. |
POST | /api/v2/moments/update | Change title and/or note. Body: moment_id, optional game_id. |
POST | /api/v2/moments/delete | Remove a timeline item. Body: moment_id, optional game_id. |
GET | /api/v2/sessions/recap | Return the latest local session recap when recap tracking is enabled. |
Example:
curl -s -X POST "$BASE/api/v2/moments" \
-H "X-OpenBox-Token: $TOKEN" -H 'Content-Type: application/json' \
-d '{"game_id":"GAME_ID","title":"Boss attempt","note":"Try the left route next time","trigger":"manual"}'
Record That: clips and reels
The clip capture route uses the configured OBS replay buffer when enabled and falls back to a local screenshot clip when OBS is unavailable. Returned clip paths are approved local media paths, not arbitrary file reads.
| Method | Route | Purpose |
|---|---|---|
GET | /api/v2/clips?game_id=GAME_ID | List the bounded clip collection for a game. |
POST | /api/v2/clips/capture | Capture a replay or screenshot fallback. Body: game_id, optional launch_id. |
GET | /api/v2/reels?game_id=GAME_ID | Build a deterministic reel manifest from the game's clips and moments. |
POST | /api/v2/reels/create | Queue local reel creation. Body: game_id, optional year. |
Time Machine and query grammar
Time Machine is journal-backed and bounded. Reads do not mutate the library. Reverts are preview-first: the response contains a plan and base_token; send that reviewed plan back with apply: true and the current base token. A stale plan returns 409 and must be previewed again.
| Method | Route | Purpose |
|---|---|---|
GET | /api/v2/library/time-machine/events | Page journal events. Optional query: days, kind, game_id, offset, limit. |
GET | /api/v2/library/time-machine/as-of?date=YYYY-MM-DD | Materialize the bounded library view at a date. |
POST | /api/v2/library/time-machine/revert | Preview or apply a revert. Body: event_id, optional game_id, fields, undo; add apply: true and the returned base_token to apply. |
GET | /api/v2/library/time-machine/compare?after=YYYY-MM-DD&before=YYYY-MM-DD | Read-only diff of the library at two journal dates. after is required; omitting before diffs against an empty baseline, so every game reads as added (a restore-point listing) and removed/changed are zero. Optional fields (comma-separated) bounds which catalog fields are compared; limit pages each list. Requires the library journal. Reversed dates return 400 TM_INVALID_DATE. (v1.15.0+) |
POST | /api/v2/library/query/parse | Parse a natural-language query into deterministic filters without changing library state. |
GET | /api/v2/library/trash | List soft-deleted library entries. |
POST | /api/v2/library/trash | Soft-delete a reviewed library entry. Body: game_id. |
POST | /api/v2/library/trash/restore | Restore a reviewed trash entry. |
POST | /api/v2/library/trash/purge | Permanently remove a trash entry. Treat this as destructive. |
Preview a revert before applying it:
curl -s -X POST "$BASE/api/v2/library/time-machine/revert" \
-H "X-OpenBox-Token: $TOKEN" -H 'Content-Type: application/json' \
-d '{"event_id":"EVENT_ID"}'
Backlog Radio and launcher trophies
Backlog Radio and Radar use local library and history data. They do not call a remote recommendation service.
| Method | Route | Purpose |
|---|---|---|
GET | /api/v2/insights/radio | Read or lazily refresh the managed recommendation playlist. |
POST | /api/v2/insights/radio/refresh | Force a fresh local recommendation set. |
GET | /api/v2/insights/radar | Return progress-oriented backlog candidates. |
POST | /api/v2/insights/radar/park | Park one candidate. Body: game_id. |
GET | /api/v2/insights/trophies | Return the local trophy case and current rule status. |
POST | /api/v2/insights/trophies/evaluate | Evaluate and persist newly earned local trophies. |
Launcher trophies are deterministic OpenBox milestones over local library and history data. They are not RetroAchievements, do not require an account, and do not submit data to a service.
Each radio pick and radar entry carries estimated_minutes (integer, rounded): the genre-based length estimate minus already-played minutes (floor 15; unplayed games report the full estimate). Picks hydrated from the stored managed playlist carry it too, with a frontend fallback for older entries. The genre fallback when no history exists is RPG/strategy/simulation 120 min, adventure/action/shooter/platform/fighting 60 min, puzzle/card/board 30 min, and 45 min otherwise; a game fits a requested session when its median session (or the fallback estimate) is at most 1.5x the requested minutes.
GET /api/v2/insights/wrapped returns the year-in-review "wrapped" payload assembled from the session journal, playtime, and completion data. It is a pure read projection: nothing is written, so the summary can never go stale.
year is a required query parameter naming the calendar year to summarize. Omitting it returns 400 ("year is required"); a non-integer returns 400 ("year must be an integer"), and a year outside 1970-2100 returns 400 ("year must be between 1970 and 2100"). Example: /api/v2/insights/wrapped?year=2026.
Game Night party queue
Game Night builds a couch-multiplayer queue from the local library. Since 1.12.1 an empty build explains itself instead of returning a bare empty list.
| Method | Route | Purpose |
|---|---|---|
POST | /api/v2/party/queue | Build a queue. Body: players (2–8), optional minutes session budget. Returns queue, count, empty_reason, excluded. |
GET | /api/v2/party/queue | Read the persisted queue and round index. |
POST | /api/v2/party/next | Advance the round. An empty queue returns 400. |
{
"queue": [],
"count": 0,
"empty_reason": "No games support 6 players — lower the player count or add Max players metadata.",
"excluded": {"total": 120, "hidden": 4, "unusable_path": 10, "too_few_players": 96, "no_controller_or_platform": 8, "over_budget": 2}
}
empty_reason is null when the queue is non-empty; the top exclusion reason wins (empty library, too few players, no couch-ready titles, missing files, over-budget sessions, all hidden). Non-object bodies and non-integer or out-of-range players/minutes return 400.
Arcade Room and Museum kiosk
Arcade Room exposes the controller-first showroom state. The optional kiosk PIN is a convenience boundary for a local Museum presentation; it is not a replacement for the API session token or a security boundary.
| Method | Route | Purpose |
|---|---|---|
GET | /api/v2/arcade/kiosk/status | Report whether Museum kiosk mode is enabled and whether a PIN is set. |
POST | /api/v2/arcade/kiosk/pin | Set or clear a PIN. Body: a 4–12 digit pin, or clear: true; enabled may be boolean. |
POST | /api/v2/arcade/kiosk/verify | Verify a candidate PIN. Body: pin. |
Household records and sync
Household is local-first. It stores members, challenges, results, and opt-in shares in the local state and can exchange validated records through the configured mounted sync folder. It does not provide hosted accounts or real-time multiplayer.
| Method | Route | Purpose |
|---|---|---|
GET | /api/v2/household?period=all_time | Return records, challenge progress, sharing state, and a leaderboard. Periods: daily, weekly, monthly, all_time. |
GET | /api/v2/household/leaderboard?period=all_time | Return the leaderboard projection for one period. |
POST | /api/v2/household/member | Add or update a member. Body: member_id, display_name, optional avatar_color, stats_shared. |
POST | /api/v2/household/challenge | Create a challenge. Body: title; optional challenge_id, metric, target, description, game_id, deadline, participant_ids. |
POST | /api/v2/household/challenge/result | Record progress. Body: challenge_id, member_id, optional value, completed, game_id, note. |
POST | /api/v2/household/share | Publish an opt-in stats share. Body: member_id, optional period and precomputed stats. |
POST | /api/v2/household/record | Append one validated local record. Body: record. |
POST | /api/v2/household/merge | Merge a validated record list. Body: records. |
POST | /api/v2/household/sync/publish | Publish pending records to the configured folder. Optional protocol. |
POST | /api/v2/household/sync/pull | Read and merge validated records from the configured folder. Optional protocol. |
Steam Bridge and ES-DE migration
Both integrations are review-first and preserve foreign data. Preview responses include a plan or import decisions; apply only the reviewed plan against an unchanged source. A changed source returns a conflict instead of silently overwriting it. Steam Bridge reports a stale preview as HTTP 409 with STEAMBRIDGE_PREVIEW_STALE; ES-DE reports its stale-plan condition as HTTP 400 with ESDE_STALE_PLAN. In either case, preview again before applying.
| Method | Route | Purpose |
|---|---|---|
GET | /api/v2/steambridge/status | Inspect the discovered shortcuts.vdf path and OpenBox/foreign counts. |
POST | /api/v2/steambridge/preview | Build a Steam Bridge plan. Optional body: absolute path, game_ids, launcher_exe. |
POST | /api/v2/steambridge/apply | Apply a reviewed plan. Body: plan; path may also be supplied. |
POST | /api/v2/steambridge/remove/preview | Preview removal of OpenBox shortcuts. Body: targets, optional path. |
POST | /api/v2/steambridge/remove | Apply a reviewed removal plan. Body: plan; path may also be supplied. |
POST | /api/v2/import/esde/preview | Parse an ES-DE gamelist.xml. Body: absolute xml_path (or gamelist), optional options. |
POST | /api/v2/import/esde/apply | Apply a reviewed ES-DE plan after a source-digest check. Body: xml_path/gamelist, plan, and optional preview_token/options. |
SteamGridDB artwork
SteamGridDB is optional. Set STEAMGRIDDB_API_KEY in ~/.env, enable the provider in Settings, and restart OpenBox after changing the environment file. Search and apply use a bounded local cache; apply and bulk match return durable job IDs.
| Method | Route | Purpose |
|---|---|---|
GET | /api/v2/steamgrid/status | Report configuration, toggle, provider state, and cache information. |
GET | /api/v2/steamgrid/search?q=TITLE&limit=12 | Search SteamGridDB games. |
POST | /api/v2/steamgrid/test | Probe the configured provider. |
POST | /api/v2/steamgrid/info | Fetch normalized metadata and media for a result. Body: steamgrid_id. |
POST | /api/v2/steamgrid/apply | Queue artwork/metadata for one game. Body: stable id; optional steamgrid_id, fields, media, replace_existing. |
POST | /api/v2/steamgrid/match | Queue bounded bulk matching. Optional body: ids and media. Without ids, games missing a cover are selected. |
The supported media kinds are cover, background, clear_logo, icon, and banner. Provider artwork is written into OpenBox-managed media paths; credentials are never returned in API responses.
Compatibility and error handling
All routes on this page are additive v2 routes. The frozen /api/v1/* surface remains unchanged. The shared API contract still applies:
403 {"error":"Unauthorized"}means a known protected route received no valid token; an unknown route can resolve to404before auth.409is used for stale review plans or launch/state conflicts where the operation must be previewed or retried.202 {"state":"queued","job_id":"…"}means the local durable-operation manager accepted the work; query/api/v2/jobsfor progress.400responses identify validation failures with the route's error message and, for structured errors, a stablecode.503means local state or an explicitly required integration is unavailable; no destructive mutation should be inferred from the response.
See REST API overview for the request lifecycle, REST API for group navigation, and the corresponding user guides for UI-level workflows.