// OPENBOX DOCS

REST API overview

Loopback boundary, authentication, body limits, and the request lifecycle.

The Web UI starts a ThreadingHTTPServer bound to 127.0.0.1 on a random port. This page documents the shared contract every route follows and the lifecycle a request goes through. Route lists live on the group pages.

Loopback boundary

  • The server binds to ("127.0.0.1", 0), so nothing is reachable from the network.
  • OpenBoxGL does not terminate HTTPS. Use a trusted network and an external reverse proxy if you must expose it beyond the host.
  • Requests carry a 30-second socket timeout (Handler.REQUEST_TIMEOUT).
  • The static UI is served at / and /index.html; the favicon is served at /favicon.svg and /favicon.ico. Every other route requires authentication.

Authentication

All API routes require the session token. The server writes it to server.token in the data directory at startup and deletes the file on exit. Two transports are accepted:

  • Header: X-OpenBox-Token: TOKEN (preferred)
  • Query parameter: ?token=TOKEN (can leak through history and logs)

The check uses secrets.compare_digest, and unauthenticated requests to any route, known or unknown, return 403 {"error":"Unauthorized"}.

Request lifecycle

A request flows through a fixed set of stages. Knowing the order tells you which status code you will get and why:

parse + route
  ├─ unknown route? → 404 ROUTE_NOT_FOUND
  └─ known route, wrong/missing token? → 403 UNAUTHORIZED
authenticate (secrets.compare_digest)
read body (Content-Length, ≤ 65536 bytes, valid JSON object)
  ├─ too large → 400 "Request is too large."
  ├─ truncated → 400 "Request body was truncated."
  └─ not a JSON object → 400
dispatch to handler (routes.py → Handler.<method>)
  ├─ state corrupt? → 503 STATE_UNAVAILABLE "OpenBox library data needs recovery…"
  ├─ handler raises ApiError → its status + code (GAME_NOT_FOUND, MEDIA_NOT_FOUND, …)
  ├─ POST handler raises ValueError/OSError/… → 400 BAD_REQUEST {error: message}
  └─ any other exception → 500 INTERNAL_ERROR "Unexpected server error. Copy the diagnostic log…"
send JSON response (nosniff, CSP, Referrer-Policy: no-referrer)

Every error response carries a stable code and a request_id. The request id is a short per-request token that also appears in the diagnostic log, so a screenshot of an error banner can be correlated with the log line.

POST handlers re-raise ApiError unchanged and convert ValueError, OSError, TypeError, AttributeError, KeyError, IndexError, json.JSONDecodeError, GameyfinError, FileNotFoundError, RuntimeError, and subprocess.SubprocessError into 400 BAD_REQUEST with the message in error. State-corruption errors become 503 before handler dispatch. Everything else is logged and returned as 500.

This is why almost every operation failure you hit through the API is a 400 with a readable, specific message — and why a 500 is genuinely "something the developers need to see," not a normal error path.

Bodies

  • POST bodies must be JSON. Content-Length must be a valid non-negative number; bodies over 65,536 bytes are rejected with "Request is too large."; truncated bodies with "Request body was truncated.".
  • The body must decode to a JSON object; [], null, "text", and malformed JSON return 400 {"error": ...}.
  • GET requests take parameters from the query string (parse_qs); lists and repeated keys behave per parse_qs (values are lists).

Response headers

JSON responses are served with Cache-Control: no-store, X-Content-Type-Options: nosniff, Referrer-Policy: no-referrer, and a restrictive Content-Security-Policy. Media files are served with immutable cache headers, ETag, Last-Modified, Accept-Ranges, and byte-range (206/416) support; theme.css revalidates with public, max-age=0, must-revalidate and ETag.

Common errors

StatusEnvelopeWhen
403{"error":"Unauthorized","code":"UNAUTHORIZED","request_id":"…"}Missing or wrong token
400{"error": "<message>", "code": "BAD_REQUEST", "request_id": "…"}Validation failures, missing prerequisites surfaced by handlers, provider errors, unknown queue/notification actions, malformed payloads
404{"code": "ROUTE_NOT_FOUND"}Unknown route
404GAME_NOT_FOUND / MEDIA_NOT_FOUND / DOCUMENT_NOT_FOUND / PLATFORM_DOCUMENT_NOT_FOUND / BADGE_NOT_FOUNDLookups that miss
409{"error":"Download the LaunchBox metadata database first."}Metadata routes before the database exists
503{"error":"OpenBox library data needs recovery before this operation can continue.","code":"STATE_UNAVAILABLE"}Corrupt state file
500{"error":"Unexpected server error. Copy the diagnostic log from Settings and include it in your report.","code":"INTERNAL_ERROR"}Unhandled exception

Versioned surface

The stable contract lives at /api/v1/*. Legacy /api/* paths keep serving the same handlers for older clients. The v1 prefix currently covers the library, settings, launch, games, queue, tags, notifications, webhooks, playlists, sessions, saves, media, metadata, imports, emulators, profiles, themes, updates, backups, jobs, diagnostics, plugins, recovery, and filter presets. New work targets v1.

Limits that apply across routes

LimitValue
Request body65,536 bytes
Socket timeout30 seconds
Playlist members100,000 per playlist
History returned by /api/historylimit clamped to 1..500, default 100
Webhook configs32
Watch folders50

The shared read endpoint

GET/api/libraryAuthX-OpenBox-Token: TOKEN

The full public library projection, cached until the library file, media epoch, or plugin epoch changes.

Returns games, playlists, filter_presets, ra_configured, settings, discovery, and media_epoch. Each game includes every editable field (with "" defaults) plus computed flags: id (numeric index), game_id (stable), favorite, hidden, last_played, play_count, playtime_seconds, path_exists, has_cover/has_background/…, has_saves, has_documents, has_achievements, tags, custom_fields, store_catalog, store_installed, owned, and more.

In safe mode the library hook is skipped; otherwise plugin library hooks can transform the games list before it is returned (cached for 3 seconds, invalidated on state changes).

Security notes

  • Treat server.token like a password: it grants full read/write access to the library, media, settings, and destructive operations.
  • The API can read any local file path referenced by library entries (media, documents, saves). Keep local paths and exported library data private.
  • Never log tokens or paste library exports into issues. The diagnostic log redacts credentials.

See How OpenBoxGL works for the server, state store, and lifecycle, and Configuration for the data directory.