// OPENBOX DOCS

REST API

Use the local authenticated HTTP API for automation.

The app exposes a local REST API for automation and third-party tooling. The server binds to loopback only and requires a per-launch token on every request. This page is the index; each operation group has its own reference page, and How OpenBoxGL works explains the server, the error contract, and the lifecycle.

Quickstart

Find your token and port, then call any route with the X-OpenBox-Token header:

TOKEN=$(cat ~/.local/share/openbox-game-launcher/server.token)
PORT=$(cat ~/.local/share/openbox-game-launcher/server.port)

# List the whole library
curl -s -H "X-OpenBox-Token: $TOKEN" http://127.0.0.1:$PORT/api/library | jq '.games | length'

# Launch a game by stable id
curl -s -X POST -H "X-OpenBox-Token: $TOKEN" \
 -H "Content-Type: application/json" \
 -d '{"game_id": "GAME_ID"}' \
 http://127.0.0.1:$PORT/api/launch

# Toggle a favorite
curl -s -X POST -H "X-OpenBox-Token: $TOKEN" \
 -H "Content-Type: application/json" \
 -d '{"game_id": "GAME_ID"}' \
 http://127.0.0.1:$PORT/api/favorite

Replace GAME_ID with a real game-<hex> id from /api/library. The token and port live only while the app is running; both files are deleted when the server stops.

Server and transport

  • The API listens at http://127.0.0.1:SERVER_PORT on a random port chosen at startup. The port and token are written to server.port and server.token inside the data directory and deleted when the server stops.
  • OpenBoxGL does not terminate HTTPS. The API is designed for the local host. If you expose it beyond the machine, put a trusted reverse proxy in front and understand the trust boundary first.
  • Requests time out after 30 seconds at the socket level (Handler.REQUEST_TIMEOUT).
  • JSON request bodies are limited to 65,536 bytes (Handler.MAX_BODY). A larger body returns HTTP 400 with {"error": "Request is too large."}.

Authentication

Every route except the static UI, locale JSON, and other explicitly public assets requires a token. Send it as a header:

X-OpenBox-Token: TOKEN

The query parameter token=TOKEN is also accepted but can leak through browser history, referrer logs, and proxy logs. Prefer the header in anything you write. The app scrubs ?token= from history immediately (history.replaceState) and never logs the token-bearing URL; logs use the token-free http://127.0.0.1:PORT/. The token is compared with a constant-time comparison (secrets.compare_digest). Known protected routes check the token after route resolution, so an unknown route can return 404 before authentication. A missing or wrong token on a known protected route returns 403 {"error":"Unauthorized"}. Ten failures in 60 seconds per IP return 429 {"error":"Too many authentication failures. Try again later.", "code":"RATE_LIMITED", "retry_after": 60} with Retry-After (correct token still succeeds and clears the bucket).

Response envelope

Most API handlers return JSON objects. Structured errors include an error, stable code, and short request_id; public assets, media/download endpoints, and Server-Sent Events can return bytes or streams instead of JSON. The versioned route pages document the response shape for each operation.

StatusMeaning
200Success
202Accepted; a background job was started (metadata sync, media bulk, emulator install/update, Gameyfin install)
304Not modified (conditional media and theme requests)
400Validation error; the error field names the exact problem (absolute paths are sanitized: the server log keeps the full path, the client receives the prefix before the path)
403{"error":"Unauthorized"}; missing or wrong token on a known protected route
429{"error":"Too many authentication failures. Try again later.", "code":"RATE_LIMITED"}; 10 auth failures in 60 seconds per IP, with Retry-After
404{"error":"Not found"} or a route-specific error for unknown routes or missing games/media/documents; route lookup can happen before authentication
409Required local prerequisite is missing (for example, the metadata database has not been downloaded)
416Invalid byte range on media
503{"error":"OpenBox library data needs recovery before this operation can continue."} when the state file is corrupt
500{"error":"Unexpected server error. Copy the diagnostic log from Settings and include it in your report."}

The 400 path catches ValueError, OSError, TypeError, AttributeError, KeyError, IndexError, json.JSONDecodeError, GameyfinError, FileNotFoundError, RuntimeError, and subprocess.SubprocessError from handlers, so most operation failures surface as 400 with a readable message. A corrupt state file raises 503 before the handler runs. Anything else is a 500 with a logged exception.

Route groups

Each group has its own reference page with per-endpoint cards.

Security notes

  • Keep exported library data and local paths sensitive. The API can read game paths, media files, documents, and the diagnostic log.
  • Secrets (webhook secrets, Gameyfin password, RetroAchievements and EmuMovies credentials) are never returned: credential-bearing settings expose *_set boolean flags instead, and the diagnostic log redacts tokens, passwords, and API keys.
  • Restore, deletion, and cleanup operations are destructive by design. They validate archive paths, reject symlinks, and refuse to run while games are running where the operation says so.

Examples

List the library with the header token:

curl -H "X-OpenBox-Token: $TOKEN" http://127.0.0.1:$PORT/api/library

Launch a game by stable id:

curl -X POST -H "X-OpenBox-Token: $TOKEN" \
 -H "Content-Type: application/json" \
 -d '{"game_id": "GAME_ID"}' \
 http://127.0.0.1:$PORT/api/launch

Where $TOKEN and the port come from the running server (cat ~/.local/share/openbox-game-launcher/server.token). See Configuration for the data directory and the application repository for the current route contract (routes.py).