Building a client
Tokens
- Store the whole
TokenPair(both tokens and both expiry times); keep it until a new pair is stored. - Refresh when less than 60 s of the access token are left (compare
access_expires_atwith the clock), and on any 401token_expired(then retry the request once). - Refresh single-flight: one refresh at a time per refresh token, across every tab, window or
thread that shares it. A second use within 30 s answers the same pair; a later reuse logs the
player out everywhere (
refresh_token_reused). - On 401
unauthorized/refresh_token_reusedor 403bannedfrom refresh: drop the tokens and show the login screen. On network errors and 5xx: keep them and try again later. - Log out with the refresh token in the body, so logout works after the access token expired.
- In a browser, tokens in
localStorageare readable by any script on the page: keep the page free of untrusted scripts (or keep the tokens in memory and log in per visit).
HTTP
- Send
Content-Type: application/jsonand a JSON body ({}at least) on routes with a body. - Branch on
error.code, never onmessage; treat unknown codes like their HTTP status. - On 429 wait
details.retry_after_ms(orRetry-After); on 503 retry with backoff. - Use
if_versionfor saves that two devices may write. - Ignore unknown fields; treat absent and
nullthe same. - Optionally send
x-net-backend-protocol: 1and checkGET /v1/infoat start-up (min_protocol≤ your version ≤protocol, and themodulesyou need).
WebSocket
- One connection per client; authenticate with the header or the first-message
authwithin 5 s; send requests only afterauth.ok. - Number requests with a counter; match answers by
id; every request gets exactly one answer. - Classify frames: numeric
idand (okor notype) = answer; otherwise bytype(auth.ok,auth.failed, pushes). Ignore unknown push kinds. - Never reconnect automatically after 4000–4099 (4001: one refresh + one new connection at most). Reconnect with exponential backoff and jitter after everything else (1000, 1001, 1006, 1008, 1009, 1011, 1013).
- After every (re)connect: join the chat rooms again and reload what may have been missed.
- Do not resend
chat.sendautomatically; use a randomnonceand dedupe by message id. - Keep messages under 1 MiB and below 20 frames per second (burst 40).
- Answer pings (browsers and most libraries do it for you); expect the server to drop a connection that is silent for 60 s.
- At most 5 connections per user: a 6th closes the oldest with 4009 (tell the player).
What to persist between runs
- The
TokenPair(secret: store it like a password). - Optionally the account (
id,display_name) for an instant start, refreshed withGET /v1/account. - The storage versions you last loaded or wrote, when the game keeps local copies of saves.
- Nothing about WebSocket state: room membership and presence are rebuilt after every connect.
Generated from API.md (repository commit e25edb9, file sha256 3db87f7bba70).