Latency, Reliability and Performance

Multiplayer feel is a latency story. This page documents how the API's transports behave, how to measure them yourself with reproducible tooling, and the numbers we publish — always with environment attached, never as a marketing average.

Three lanes, three latency profiles

REST (HTTPS): one round trip plus TLS/handshake overhead per call. Used for everything stateful and infrequent — registration, lobby management, leaderboards, storage. The right budget is tens to a few hundred milliseconds per call, dominated by your distance to the API and connection reuse.

WebSocket lane: one persistent TLS connection; after the handshake each message is a single TCP segment in each direction — near-RTT, ordered and reliable. This is the browser's only lane and the control channel for native clients.

UDP lane (MUR1): connectionless datagrams after a one-time BIND. No retransmission, no head-of-line blocking — the lowest and most consistent latency of the three, at the cost of possible loss and reordering. Reserved for traffic where a stale packet is worse than a missing one: positions, analog input, ephemeral state. Node.js and the native SDKs can bind it; browsers can't (they fall back to WS — see WebGL).

Measure, don't guess: mp bench

The JavaScript SDK ships a CLI with a benchmark subcommand that times real requests against the production API and reports percentiles. It exists so that any number quoted on this site — or in your own capacity planning — is reproducible by you, from your network, today:

MP_API_TOKEN=your_token mp bench 100
{
  "requests": 100,
  "min_ms": 34.2,
  "p50_ms": 41.7,
  "p95_ms": 88.3,
  "p99_ms": 121.5,
  "max_ms": 240.9
}

The same thing in two lines of SDK code: time mp.time.get() in a loop, sort the samples, read percentiles — that is the entire method.

How to benchmark honestly

A latency number without context is noise. When you measure — yours or ours — record: the client environment (machine, OS, runtime version), server region, network path (home broadband, datacenter, mobile), sample count, measurement method (wall-clock around the awaited call, not ping), the percentile breakdown, and the date and API version. Averages hide the tail; p95/p99 are where player experience actually lives.

Watch for traps: cold TLS handshakes inflate the first request (measure warm connections, or report cold separately); sequential awaits understate what concurrency does to your p99; and a benchmark from a datacenter VM flatters results versus a residential ISP. The server network page lists the regions the platform operates in — measure against the region your players will actually hit.

Timeouts, retries and the rate limiter

SDK default: 30-second request timeout. A timeout or connection reset surfaces as a TransportError (JS) / thrown transport error (other SDKs) — distinct from an API error body, so your code can tell "server answered no" from "no answer came". GET requests retry once by default on transport failure and 5xx; POSTs never auto-retry, because retrying a non-idempotent write (register, join, score submit) can double-apply it — if you need retry semantics on writes, build idempotency into your flow (check current state first).

Rate limits return HTTP 429 with a normal error body. Handle it by backing off and queueing rather than hammering — the limiter is per-player/per-endpoint, and heartbeat-style calls are the ones most likely to trip it if you loop too fast. The realtime token endpoint, for example, is capped at 10 calls/minute/player.

On the realtime channel the server closes idle WebSockets at ~60s — the SDK auto-heartbeats every 20s, and a closed connection should trigger a token refresh + reconnect rather than a retry loop on the dead socket.

Design rules that survive the real internet

  • Keep REST off the hot path — it's for moments, not per-frame state. Anything at 10–60Hz belongs on the realtime channel.
  • Ephemeral high-rate state goes on UDP (native/Node); durable or ordered events go on WebSocket. If a lost update is a bug, it's WS traffic.
  • Batch when you can — one message with 8 player positions beats 8 messages.
  • Design for the p99, not the mean. A game that feels fine at 40ms average and unplayable at its 300ms tail has a tail problem.
  • Never promise "global low latency". Regions and last-mile networks decide it — publish your measured numbers with their environment attached, like this page does.