This is an overview of protocol 1.2, which is language-neutral and additive over 1.1. The full contract, with signing test vectors, is maintained alongside the agents; write to [email protected] for a copy. Base URL: https://serverstats.cc/ingest/v1, HTTPS only. Every response carries X-SS-Protocol and X-SS-Time (server time in Unix milliseconds); errors are {"error":{"code","message","details"?}} and agents branch on code.
Identity
An agent has one identity: an Ed25519 keypair it generates itself. The private key never leaves the machine; the server stores only the public key.
- The owner creates a claim code (
SS-XXXX-XXXX-XXXX, 60 random bits, single use, 30 minutes). POST /enrolwith{claim, public_key, game, agent_version, platform}.public_keyis the raw 32 bytes in standard base64 with padding. The answer holdsserver_id,action_public_keyand the action key set. A new claim code with a new key replaces the old key at once.
Roblox cannot hold a signing key, so experiences use an experience key (ssx_) to register a per-instance key at POST /instances.
Signed requests
Every request except /enrol, /instances, /action-keys and /health carries:
| Header | Value |
|---|---|
X-SS-Server | the server_id |
X-SS-Ts | current time in Unix milliseconds, corrected by the last X-SS-Time (must be within 5 minutes) |
X-SS-Nonce | 16 to 128 characters of [A-Za-z0-9_-], fresh for every request including retries |
X-SS-Sig | base64 of Ed25519(private_key, "ss1." + ts + "." + nonce + "." + hex(sha256(body))) |
body is the bytes as sent (the gzip bytes if compressed, none for a GET). A nonce cannot repeat within 10 minutes. The rate limit is per server across all signed requests: a bucket of 30 refilling 1 per 2 seconds.
Sending data
POST /batch (gzip recommended, 1 MiB on the wire, 16 MiB decompressed) with {agent, samples, events, sessions, players, player_props, entities, secrets}, all arrays optional. Typical agents send every 10 seconds.
{"agent":{"game":"minecraft","version":"serverstats-paper/1.1.2","platform":"paper","server_name":"Survival EU"},
"samples":[{"m":"tick_rate","t":1767225600000,"v":19.8,"d":{"window":"1m"}}],
"events":[{"type":"player_death","t":1767225598000,"pid_raw":"069a79f4-44e9-4726-a5be-fca90e38aaf5","props":{"world":"world"}}],
"sessions":[{"sid":"a1b2c3","pid_raw":"069a79f4-44e9-4726-a5be-fca90e38aaf5","join":1767225000000,"leave":1767225590000,"country":"GB"}]}Limits: 10,000 samples, 5,000 events and 2,000 sessions per batch; timestamps are Unix ms within 7 days back and 5 minutes forward. Agents reduce a player's IP to a country locally and never send IPs. Ingest hashes pid_raw with the organisation's key and stores the original encrypted. The 200 answer reports accepted and dropped counts and config_rev. A drop is still success: do not resend. 400 invalid_batch will never succeed, so drop it. A paused server is answered 200 with status: "paused" and nothing stored. Retries back off, and offline agents buffer to disk (50 MB, 7 days).
Names: plain snake_case names are stored as custom.<name> unless they match a catalogue key for the game.
Config, inventory and actions
POST /inventorydeclares detected plugins and currencies.GET /configreturns a signed config document (source selection, thresholds, running experiments) when theX-SS-Config-Revheader says it changed. Agents verify its Ed25519 signature before applying.GET /configmay carry an unsignedupdate_availablehint (statusoutdated,unsupportedorknown_bad). Agents may show it to admins, must never download or install from it, and must not trust it.- Moderation actions are opt-in per server.
GET /actions?wait=Nlong-polls; each action is signed with the action key (a different key from the agent's), and the agent must verify it and the server's pinned keys before doing anything, thenPOST /actions/ack. Restart and automation actions use the same channel. GET /healthis unauthenticated and returns the protocol version.
Error codes agents see
bad_auth_headers, stale_timestamp (use details.server_time), unknown_server, not_enrolled, bad_signature, replayed_nonce (all 401), wrong_game (400), payload_too_large (413), unsupported_encoding (415), rate_limited (429, honour Retry-After) and internal (500, retry with backoff). Enrolment errors (claim_invalid, claim_used, claim_expired, claim_revoked) are permanent; never retry them automatically.
Capabilities
Agents announce agent.caps such as config.v1, domains.v1 (sends typed domain events with src, amount, cur, subj) and experiments.v1. The server never needs them to accept data. Event types, channels and sources are kept in a machine-readable catalogue (metrics, events, sources and enums) with JSON Schemas.