Denis Cercasin
Reference documentation
Table of contents
Conventions
- Base URL:
http(s)://<host>:<PORT><ORIGIN_PATH>— locallyhttp://localhost:8080/api, productionhttps://roomofhorror.de/api. - Parameters are passed as query parameters; request bodies are not used.
- Authentication: protected endpoints expect a JWT access token in the
Authorizationheader (raw token, noBearerprefix). Unity endpoints authenticate with thedeviceUUIDquery parameter instead. - Validation: every request is validated by a Zod schema (
schemas/requests.ts); invalid input returns400with a description before any handler logic runs. - Rolling tokens: authenticated game endpoints return a fresh
accessTokenwith each successful response. - DEV_MODE: when the API runs with
DEV_MODE=true, login/signup/recovery responses additionally contain the token that would normally only be sent by e-mail (authToken/recoveryToken) — this enables local development without an SMTP server. - A ready-to-use Postman collection ships in the repository:
room-of-horror-api/roh_2_1.postman_collection.json.
All error responses share the shape { "description": "<human readable message>" } (messages in messages/APIMessages.ts). Unexpected failures are logged to the errors table and return 500 with an error reference.
Account endpoints
| Method & path | Auth | Parameters | Purpose |
|---|---|---|---|
POST /account/signup | – (rate limited) | email, givenName, surname, address | Creates an account, sends signup mail with auth link. 409 on duplicate e-mail |
POST /account/login | – (rate limited) | email | Sends login mail with auth link; returns one-time sessionID. 404 unknown e-mail, 408 cooldown active |
GET /account/auth | Auth token (header) | – | Redeems the e-mailed auth token (single-use, blacklisted) and activates the account. Returns accessToken. 401 invalid/used token |
GET /account/use-session-id | – | sessionID | Polls the one-time session ID created at login; once the mail link was clicked, returns accessToken. 401 invalid/expired |
POST /account/get-recovery | – (rate limited) | email | Sends recovery mail. 404 unknown e-mail, 408 cooldown |
GET /account/set-recovery | Recovery token (header) | – | Redeems recovery token, rotates auth_uuid (invalidates all old tokens), returns fresh accessToken |
GET /account/retrieve | Access token (header) | type = check | user | game | Returns account validity, profile data, or the joined game |
Example — login flow in development:
curl -X POST "http://localhost:8080/api/account/login?email=test@example.org"
# → { "description": "...", "sessionID": "...", "authToken": "<only in DEV_MODE>" }
curl -H "Authorization: <authToken>" "http://localhost:8080/api/account/auth"
# → { "description": "...", "accessToken": "<JWT>" }
Game endpoints
| Method & path | Auth | Parameters | Purpose |
|---|---|---|---|
GET /game/get-game | Access token or deviceUUID | deviceUUID (optional) | Returns the full game state. The user variant additionally contains found errors, coordinates, mistake positions, the decrypted deviceUUID, the live evaluation object and a fresh accessToken. 404 no game |
POST /game/join-game | Access token | gameCode | Links the logged-in user to the game shown in the VR lobby. 404 unknown code or already in use |
PUT /game/edit-game | Access token | target, value | Universal game mutation — see targets below. 400 invalid value or game already running |
POST /game/error-found | deviceUUID | errorID | Marks an error as found. Ends the game (409 + evaluation) when it was the last one. 400 not selected / already found, 409 mistake budget exhausted |
POST /game/add-mistake | deviceUUID | mistakeCor = [x,y,z] | Records a wrong click; ends the game when the mistake budget is reached |
POST /game/stop-game | deviceUUID | – | Ends a running game from the headset and persists the evaluation. 409 if not running |
POST /game/create-game | deviceUUID | errorPos | Creates a game row manually (normally done by device-init) |
edit-game targets
target | value | Validation |
|---|---|---|
status | LOBBY | SETUP | STARTED | ENDED | Starting resets found errors and mistakes |
errors | JSON array of error IDs, e.g. [1,23,24] | Each ID must be allowed for the game’s current patient |
mistakes | Number ≤ MAXIMUM_MISTAKE_AMOUNT | Mistake budget (health bar) |
patientId | patient_a … patient_f | Also sets the matching patientModel; rejected while a game is running |
patientModel | Unity model key | Direct model override; rejected while a game is running |
uuid | – | Sign-out: unlinks the user from the game |
Patient endpoint
| Method & path | Auth | Purpose |
|---|---|---|
GET /patient/get-patients | Access token | Returns all active patient profiles (demographics, medical master data, accent color, allowedErrorIds, defaultErrorAmount) for the selection UI |
Device endpoints (Unity)
| Method & path | Auth | Parameters | Purpose |
|---|---|---|---|
POST /device/device-init | deviceUUID | errorPos | Headset bootstrap: creates the game on first contact or reloads it (status LOBBY/SETUP depending on linked user); returns the gameCode. Updates stored error positions |
POST /device/update-error-positions | deviceUUID | errorPos | Re-sends all error world positions after the room was configured for a patient |
errorPos format: JSON array of [errorId, x, y, z] tuples generated by Unity’s ErrorObject.getAllErrorCor().
WebSocket (Socket.IO)
- Path:
<ORIGIN_PATH>/socket.ioon the same server; CORS restricted to the frontend origins. - Join: emit
joinGameRoomwith{ token }(frontend) or{ deviceUUID, clientType: "vr" }; the server resolves the game and joins the socket to roomsession:<game_uuid>.
| Direction | Event | Payload highlights |
|---|---|---|
| client → server | joinGameRoom, clientReady | Join payload; ack returns the current GameSocketStatePayload |
| client → server | requestGameState | Re-sync request |
| client → server | leaveGameRoom | Leaves the room |
| server → client | gameStateUpdated | Full state: status, patient, errors, found errors, coordinates, mistakes, live evaluation |
| server → client | errorFound | errorId + full state |
| server → client | mistakeMade | mistakeCoordinates + full state |
| server → client | gameStarted / gameEnded | Lifecycle + full state |
| server → client | playerJoined/Left, deviceConnected/Disconnected | Presence with clientType |
| server → client | reconnectRequired, error | { code, message, timestamp } |
Type definitions for every payload: room-of-horror-api/websocket/Events.ts (mirrored in room-of-horror-frontend/src/services/gameSocket.events.ts).
Environment variables
API (room-of-horror-api/.env.dev / .env.prod)
Validated at startup by utils/Config.ts; missing or malformed values abort with [CONFIG] Invalid environment configuration. See CONFIGURATION.md for secret generation commands.
| Group | Variables | Notes |
|---|---|---|
| Mode | USE_ENV, DEV_MODE | DEV_MODE=true disables HTTPS and exposes dev-only token responses |
| JWT | AUTH_KEY/EXP, ACCESS_KEY/EXP, RECOVERY_KEY/EXP | Secrets ≥ 32 bytes, durations like 15min, 60d |
| Session | SESSION_ID_LENGTH, SESSION_ID_VALIDITY_SPAN | One-time login handover |
| Server | PORT, ORIGIN_PATH, FRONTEND_ORIGIN, FRONTEND_ORIGIN_W, FRONTEND_PORT | CORS allow-list is built from the frontend origins |
| PostgreSQL | DATABASE_URL or PGHOST/PGPORT/PGDATABASE/PGUSER/PGPASSWORD, PGSSLMODE | Standard pg variables |
| Throttling | COOLDOWN, ENABLE_RATE_LIMIT | Mail cooldown + rate limiter switch |
OUTGOING_MAILSERVER, MAIL_SMTP_PORT, NO_REPLY_EMAIL(_PASSWORD), MAIL_NAME, … | Required for login/signup/recovery mails | |
| Crypto | ENCRYPTION_KEY (32 bytes), ENCRYPTION_IV (16 bytes) | AES-256-CBC for game codes / device UUIDs |
| Game | MAXIMUM_ERROR_AMOUNT (highest error ID, 31), MAXIMUM_MISTAKE_AMOUNT | Must match Unity scene content |
| Devices | WHITELISTED_DEVICE_UUIDS | _-separated; empty disables the whitelist |
| TLS | USE_HTTPS, CERT_PATH, KEY_PATH | Production only |
Frontend (room-of-horror-frontend/.env.dev / .env.prod)
Validated by src/utils/Config.ts. All values are public (bundled into the browser build) — never put secrets here.
| Variable | Purpose |
|---|---|
REACT_APP_USE_ENV, REACT_APP_MAINTENANCE | Env check; maintenance mode switch |
PORT / REACT_APP_PORT, REACT_APP_ORIGIN | Dev server + own origin |
REACT_APP_BACKEND_ORIGIN, REACT_APP_BACKEND_PORT, REACT_APP_BACKEND_PATH | API base URL parts |
REACT_APP_COOLDOWN | Client-side request cooldown (keep ≥ backend COOLDOWN + 1000) |
REACT_APP_SESSION_ID_INTERVAL, REACT_APP_SESSION_ID_DURATION | Login polling cadence and timeout |
Unity (Assets/Scripts/Env.cs)
Configured in the Unity Inspector, not via files:
| Field | Purpose |
|---|---|
backendOriginValue | API base URL incl. trailing / (default https://roomofhorror.de/api/; set to http://localhost:8080/api/ for local work) |
useLocalTestDeviceUUID / localTestDeviceUUID / deviceUUIDOverride | Device identity; without overrides a UUID is generated once and persisted in PlayerPrefs |
devModeEnabled, leaveLobbyPermEnabled | Debug flags / lobby-leave permission |
Information sources
room-of-horror-api/utils/Endpoints.ts,endpoints/**,schemas/requests.ts,messages/APIMessages.ts,websocket/Events.ts,utils/Config.ts,.env.exampleroom-of-horror-frontend/.env.example,src/utils/Config.ts,src/services/*room-of-horror-unity/Assets/Scripts/Env.cs,EndpointInteraction.cs- Root
CONFIGURATION.md
Suggested improvements
- Publish an OpenAPI specification generated from the Zod schemas (e.g. via
zod-openapi) so the Postman collection and this page cannot drift from the code.
Last build: 19 Aug 2026, 05:20+00:00