# Gaming Stats API — Epic Version: 3.8.3 ## Overview Base path: /epic Read a cached Epic Games owner library with claimed and uninstalled PC base games, catalog artwork, and Epic-reported playtime. Website requests never call Epic or trigger refreshes. This is an unofficial launcher-protocol integration and can require reconnection if Epic changes or revokes the session. Examples are illustrative, not live account data. ## Authentication Every data endpoint requires X-API-Key from a trusted website server or backend proxy. Documentation is public. Missing or invalid read keys return HTTP 401. Data responses use Cache-Control: private, no-store. The read key cannot connect, refresh, disconnect, select an account, or retrieve credentials. Never put API_KEYS, Epic authorization codes, or session tokens in browser JavaScript. ## Requests GET /epic/library — complete cached owned base-game collection, coverage and totals GET /epic/games/:gameId — one cached owned game Use providerGameId from the library. It is an opaque 43-character identifier. No request body or pagination argument is accepted; the refresh worker completes upstream pagination before publication. ## Request example EPIC_API_KEY is one of this service's API_KEYS read keys, not an Epic credential. ```sh curl --fail-with-body "$API_BASE_URL/epic/library" \ -H "X-API-Key: $EPIC_API_KEY" ``` ## Library response coverage.inventoryComplete and catalogComplete describe the published snapshot. Epic playtime is converted from integer seconds to minutes without early rounding. playtimeStatus is known, unknown, ambiguous, or unavailable. A missing Epic playtime record remains null/unknown; it never becomes zero. Explicit upstream zero remains known zero. imageUrl and artwork.url retain the preferred compatibility image. artwork.images preserves every safe catalog key image with type, URL, alt text, dimensions, byte size, upload timestamp, and MD5 checksum when supplied. Totals sum only known records and do not merge Steam, Playnite, PSN, editions, or aliases. Add-ons, private sandboxes, Unreal Engine assets, records without an app artifact, and unknown classifications are excluded and counted. ```json { "schemaVersion": 1, "provider": "epic", "accountRef": "owner", "status": "ready", "stale": false, "lastAttemptAt": "2026-09-17T12:00:00.000Z", "lastSuccessAt": "2026-09-17T12:00:00.000Z", "nextRefreshAt": "2026-09-17T12:15:00.000Z", "coverage": { "kind": "owned-pc-base-games", "inventoryComplete": true, "catalogComplete": true, "catalogStale": false, "playtimeStatus": "available", "missingPlaytimeMeans": "unknown", "unmatchedPlaytimeRecords": 0, "excluded": { "addons": 0, "engineAssets": 0, "unknownClassification": 0, "privateRecords": 0, "noAppArtifactRecords": 0 } }, "totals": { "gameCount": 1, "playedGameCount": 1, "knownPlaytimeGameCount": 1, "unknownPlaytimeGameCount": 0, "ambiguousPlaytimeGameCount": 0, "totalPlaytimeMinutes": 90.5 }, "games": [ { "providerGameId": "EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE", "name": "Example Game", "imageUrl": "https://cdn1.epicgames.com/example/wide.jpg", "artwork": { "url": "https://cdn1.epicgames.com/example/wide.jpg", "images": [ { "type": "DieselGameBoxWide", "url": "https://cdn1.epicgames.com/example/wide.jpg", "alt": "Example landscape art", "width": 2560, "height": 1440, "sizeBytes": 123456, "uploadedAt": "2026-09-01T12:00:00.000Z", "checksumMd5": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" }, { "type": "DieselGameBoxTall", "url": "https://cdn1.epicgames.com/example/tall.jpg", "alt": "Example portrait art", "width": 1200, "height": 1600, "sizeBytes": 654321, "uploadedAt": "2026-09-01T12:00:00.000Z", "checksumMd5": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" } ] }, "playtimeMinutes": 90.5, "playtimeStatus": "known", "lastPlayedAt": null } ] } ``` ## Game response A valid ID that is absent from the current cached library returns HTTP 404. The game endpoint repeats the library snapshot freshness metadata and returns the same normalized game record. Epic achievements, current presence, installation state, and reliable last-played time are not included in this release. ```json { "schemaVersion": 1, "provider": "epic", "accountRef": "owner", "status": "ready", "stale": false, "lastAttemptAt": "2026-09-17T12:00:00.000Z", "lastSuccessAt": "2026-09-17T12:00:00.000Z", "nextRefreshAt": "2026-09-17T12:15:00.000Z", "game": { "providerGameId": "EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE", "name": "Example Game", "imageUrl": "https://cdn1.epicgames.com/example/wide.jpg", "artwork": { "url": "https://cdn1.epicgames.com/example/wide.jpg", "images": [ { "type": "DieselGameBoxWide", "url": "https://cdn1.epicgames.com/example/wide.jpg", "alt": "Example landscape art", "width": 2560, "height": 1440, "sizeBytes": 123456, "uploadedAt": "2026-09-01T12:00:00.000Z", "checksumMd5": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" }, { "type": "DieselGameBoxTall", "url": "https://cdn1.epicgames.com/example/tall.jpg", "alt": "Example portrait art", "width": 1200, "height": 1600, "sizeBytes": 654321, "uploadedAt": "2026-09-01T12:00:00.000Z", "checksumMd5": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" } ] }, "playtimeMinutes": 90.5, "playtimeStatus": "known", "lastPlayedAt": null } } ``` ## Freshness and errors HTTP 200 serves ready or bounded stale snapshots. HTTP 400 means malformed gameId. HTTP 401 means a missing or invalid service read key. HTTP 404 means the ID is not in the cached Epic base-game library. HTTP 503 means disabled, not-configured, reconnect-required, unavailable, or expired data. Never display unavailable data as an empty library or zero playtime. status, stale, lastAttemptAt, lastSuccessAt, and nextRefreshAt describe the snapshot. ## Refresh and storage Library and playtime target a 15-minute cadence. Sanitized catalog metadata is cached for 24 hours. Completed snapshots can remain stale for up to 24 hours after a transient failure. Jobs use persistent due times, bounded requests, backoff, a single SQLite writer lock, and one controlled token recovery attempt. Rotating access and refresh tokens are AES-256-GCM encrypted in a separate private SQLite file on GAMING_DATA_DIR. Raw Epic responses and credentials are not published. Run one always-on Railway replica per session. ## Owner connection Deploy with ENABLE_EPIC=false. Configure EPIC_EXPECTED_DISPLAY_NAME, GAMING_DATA_DIR on the persistent volume, and the existing private GAMING_ENCRYPTION_KEY. Open a full railway ssh shell and run npm run epic:connect:production. The CLI prints an official epicgames.com login URL. Sign in and complete 2FA only on Epic, then paste only the returned authorizationCode or Epic JSON into the hidden terminal prompt. Do not paste it into chat, command arguments, screenshots, or website routes. Run epic:refresh and epic:status before enabling the provider. The gaming PC does not need to remain running. ```sh railway ssh # After the remote prompt appears: npm run epic:connect:production npm run epic:refresh npm run epic:status exit railway variable set ENABLE_EPIC=true ``` ## Disconnect and recovery npm run epic:disconnect revokes the current Epic access session before deleting local credentials, cached metadata, and snapshots. If the session cannot be renewed or revoked, fix connectivity and retry. npm run epic:disconnect -- --local-only is an explicit recovery option that removes local data but cannot revoke Epic remotely; then use Epic account security controls. A token rotation interrupted before durable replacement becomes reconnect-required rather than blindly retrying an old token.