Skip to main content

The HTTP surface at a glance

The full request surface, grouped by the three request spaces. Every /api/engine/* route requires a Firebase bearer; /api/bot/action is HMAC-authenticated; the web routes are public.

This page is the map. For per-operation request/response schemas, see the generated HTTP API reference, or read the openapi.json spec directly.

Client API — /api/engine

Reads (D1-only, never wake a DO):

Method + pathPurpose
GET /lobbyPublic joinable games, newest first
GET /games/mine?bucket=active|finishedThe caller's games
GET /games/{id}One game's summary (capability read; never state)
GET /games/{id}/frames?from=&to=Version-range frames — live gap recovery and finished-game replay
GET /players?ids=Batch public identity (≤ 50), never email
GET /botsThe bot catalog
GET /me · GET /me/ratings · GET /me/rating-historyThe caller's own profile / ratings
GET /friends · GET /friends/requests · GET /friends/gamesSocial lists
GET /users/search?q=Friend-picker search (registered only)

Game lifecycle (Commands to the DO; policy at the edge, integrity in the DO):

Method + pathPurpose
POST /games · POST /games/soloCreate (Worker-direct D1) · create-and-start vs bots
POST /games/{id}/join · POST /games/join-by-codeJoin
POST /games/{id}/leave · /cancel · /add-bot · /startWaiting-room commands
POST /games/{id}/action · /forfeitActive play (carry the caller's seat)
GET /games/{id}/socketWebSocket upgrade (?token= auth); frames + roster snapshots

Profile / account / devices / social writes:

Method + pathPurpose
PUT /me/username · PUT /me/avatar · DELETE /meRename · upload avatar · delete account
PUT /me/devices · DELETE /me/devices/{fid}FCM device register / deregister
POST /friends/requests · /requests/{id}/accept · DELETE /friends/{id}Friend request / accept / remove
POST + DELETE /friends/{id}/blockBlock / unblock

Bot webhook — /api/bot

POST /api/bot/action — an external bot submits a move, authenticated by the Eigen-Signature HMAC over the exact body. See External-bot HMAC.

Public web

GET /.well-known/assetlinks.json · apple-app-site-association · GET /join/:shortCode · GET /game/:gameId (share/landing) · GET /avatars/:uid (when avatars enabled).

When site is configured: GET / (landing) · GET /terms · GET /privacy · GET /delete-account · GET /sitemap.xml · GET /robots.txt · GET /site.webmanifest. Each is overridden by a matching public/ file.

GET /health is always mounted and is deliberately absent from openapi.json; see Deploying.

The error model

Every failure is one JSON shape — { error, code? } — with the HTTP status carrying the coarse class and the optional stable code carrying the machine reason a client keys retry/resync UX off. Handlers only ever return their declared 200 shape; a failure is an HttpError throw (or a kernel/lobby rejection converted to one) rendered by the app-level error handler.

StatusMeaningRepresentative codes
400Client mistakeinvalidPayload, illegalMove
401Missing/invalid token
403Ownership/permission refusalnotCreator, notParticipant
404No such game/userunknownGame
409State conflict — resync and retrystateUpdated, notActive, notReady, expired, notPending, gameFull, alreadyJoined, notJoinable, creatorCannotLeave, schemaUnsupported
413 / 415Avatar too big / wrong type
422Assertion mismatch (e.g. rated)
429Rate limitedrateLimited
500Server fault (game-hook bug, storage)
502Account deletion upstream failure (intact; retry)

Two reject codes are not errors and never reach the client as failures: abstain (a system timeout that lost its race — a clean no-op) and the accepted-lobby-staleness codes, which a client resolves by resyncing. Kernel rejections are values, not exceptions — recomputing one is always sound, so they are never cached the way accepted commands are.