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 /me/sync?finishedAfter=Everything a device keeps about the caller, in one read
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/games/finished?cursor=The caller's own profile / history older than a device holds
GET /friends/gamesJoinable games created by the caller's friends
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)
POST /games/{id}/socket-ticketMint a signed, game-scoped ticket valid for 60 seconds
GET /games/{id}/sessionThe caller's current session snapshot, from the game's own Durable Object
GET /games/{id}/socketWebSocket upgrade (?ticket= auth); per-seat session snapshots

Offline play (creator-only, local-origin only):

Method + pathPurpose
POST /games/localRegister and start a game already played on the device
POST /games/{id}/local/transitionsAppend a batch of the device's transition log against its current version
GET /games/{id}/localThe device's stored log (seed + raw state and actions), to resume on another device

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 lets an external bot submit a move, authenticated by the Eigen-Signature HMAC over the exact body. See External-bot HMAC.

Game and external-bot JSON request bodies are limited to 64 KiB before auth or parsing. Game configuration and action payloads must fit inside that bound.

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, notLocalBot
401Missing/invalid tokennone
403Ownership/permission refusalnotCreator, notParticipant, localOnly
404No such game/userunknownGame
409Stale view, lifecycle conflict, or version mismatchstateUpdated, notActive, notReady, gameFull, clientUpdateRequired, serverUpdateRequired
413Game/bot JSON or avatar too bignone or imageTooLarge
415Avatar type not acceptedunsupportedImageType
422A creation assertion disagrees with the authoritative game policynone
429Rate limitedrateLimited
500Server fault (game-hook bug, storage)none
502Account deletion upstream failure (intact; retry)none

Mutations use operation-specific correctness

There is no generic mutation key or permanent command-receipt protocol. Actions carry the authoritative game version they were based on and stale actions are rejected. Join and leave express a desired membership state; start and cancel are idempotent lifecycle transitions; bot seating is guarded by the lobby version it was chosen from.

Creation is the one deliberately ambiguous case. If the connection fails before the response arrives, the client reloads its game list and the player may create again. The platform does not retain permanent receipts for this rare early-stage case.

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.