Bridge protocol & API
Everything you need to talk to this server from a mod, a script or another client. The bridge is deliberately boring: line-delimited JSON over plain TCP, one reply per line, no framing, no handshake beyond a hello.
The Minecraft bridge
Default 127.0.0.1:9010. Configurable via bridge_host and
bridge_port in config/config.json.
Connection lifecycle
Every line you send gets exactly one reply line. Unknown types are acknowledged rather than rejected, so adding new event types never breaks an old client.
Server replies
| You send | You get |
|---|---|
{"type":"ping"} | {"ok":true,"type":"pong","time":...} |
{"type":"register",...} | {"ok":true,"type":"ack","registered":true,"client_id":...} |
| anything else valid | {"ok":true,"type":"ack","time":...} |
| malformed JSON | {"ok":false,"type":"error","error":"invalid json: ..."} |
pong. That is what drives the
connection pill on the dashboard and the status page.
Events the website sends you
Each of these arrives as one JSON line on a fresh connection, or on your open socket if you registered.
| type | action | Payload | When |
|---|---|---|---|
auth | login | user_id, username, remote_addr | Someone logs into the website |
auth | signup | user_id, username, remote_addr | A new account is created |
auth | logout | user_id, username | Someone logs out |
presence | join / leave | user_id, username | A browser socket connects or drops |
call | join / leave | room, user_id, username | Someone enters or leaves a call room |
call | invite | call_id, room, from, to, video | A direct call is started |
call | incoming | call_id, room, from, join_url | Pushed to a registered client being rung |
call | accept / decline | call_id, by | The other side answers or rejects |
arcade | score | username, game, game_title, score, personal_best, rank | A logged-in player finishes a run |
Receiving pushed events
A client that only wants to fire events at the website can connect, write, and disconnect. To receive pushes — which is what makes web-to-Minecraft calling work — you must register and hold the socket open.
Username matching is case-insensitive. Send a keepalive
ping every 20–30 seconds so the socket is not dropped by an idle timeout.
Minimal Java client
Run the read loop on its own thread. Never block the Minecraft client thread on socket I/O.
REST API
All responses are {"ok": true, "data": ...} or
{"ok": false, "error": "..."}. Session cookie authentication.
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /api/arcade/games | None | All 10 games with your personal best and the world best |
| GET | /api/arcade/profile | None | Your profile: XP, level, bests, achievements. Works for guests |
| POST | /api/arcade/score | None | Submit a finished run. Guests are recorded but not published |
| GET | /api/arcade/leaderboard/<game> | None | One game's board, or global for the XP board |
| GET | /api/arcade/leaderboards | None | Every board at once |
| POST | /api/arcade/claim | User | Merge a guest id into the logged-in account |
| POST | /api/support/ticket | None | Open a ticket, returns an id and lookup token |
| GET | /api/support/ticket/<id>?token= | None | Read a ticket with its lookup code |
| GET | /api/support/my-tickets | User | Tickets attached to your account |
| GET | /api/bridge/status | None | Current bridge connection state |
| GET | /api/users | User | All users with online state |
| GET | /api/friends | User | Your friend list |
| GET/POST | /api/dms/<friend_id> | User | Read or send direct messages |
| GET/POST | /api/servers | User | List, create and join community servers |
| GET | /api/kal/balance | User | Your KAL balance |
| POST | /api/minecraft/link | User | Link a Minecraft username to your account |
Submitting a score
saved_to_leaderboard is false for guests. That is
the flag the game overlay uses to decide whether to show the sign-up prompt.
Socket.IO events
Client → server
| Event | Payload |
|---|---|
presence:join | — |
join_call | {room, video} |
leave_call | {room} |
webrtc:signal | {target, type, sdp|candidate} |
call:invite | {to_username|to_user_id, video} |
call:accept | {call_id} |
call:decline | {call_id, reason} |
call:cancel | {call_id} |
media:state | {room, muted, video, screen, speaking} |
call:roster | {room} |
Server → client
| Event | Payload |
|---|---|
call:peers | {room, peers[]} |
call:peer_joined | {room, sid, username, video} |
call:peer_left | {room, sid} |
webrtc:signal | relayed verbatim |
call:ring | full invite object |
call:accepted | {call_id, room, video} |
call:declined | {call_id, reason} |
call:cancelled | {call_id, reason} |
presence:update | {user_id, online} |
bridge:update | {connected, last_ok, last_error} |
bridge:call | forwarded from Minecraft |
arcade:playing | {username, game, at} |
Data on disk
| File | Format | Holds |
|---|---|---|
data/app.db | SQLite | Users, friendships, servers, messages, KAL ledger |
data/arcade.json | JSON | Arcade scores, player profiles, XP, achievements |
data/support.json | JSON | Support tickets and replies |
data/admin.json | JSON | Admin passcode hash |
data/secret.key | Fernet key | Message encryption key |
data/flask_secret.key | Text | Session signing secret |
data/ folder together. Losing secret.key makes existing
encrypted messages permanently unreadable, and losing flask_secret.key logs everyone out.
JSON writes are atomic — write to a temp file, then replace — and
guarded by a re-entrant lock, so a crash mid-write cannot leave a truncated file. If a file is ever found
corrupt it is renamed to .corrupt rather than deleted.