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.
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).
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.
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.
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.