12 KiB
Protocol Reference
This page describes how an external tool, such as an RCON client, a bot, or a dashboard backend, talks to RconDataBridge: connecting, the option map, the request/response cycle, idempotency, rate limiting, and error codes. For the specific operations you can call, see Operations Reference.
1. Connecting
RconDataBridge has no transport of its own; it rides entirely on the
server's existing RCON connection (the standard Source RCON protocol, the
same one any RCON tool already speaks). Point your RCON client at the
server's configured RCONPort and RCONPassword as usual. Only two RCON
commands are involved:
changeoption <name> <value>, used to write an option (only ever used forRconDataBridge_Request)showoptions, used to read every option's current value (used to read everything else)
There is no dedicated bridge port and no separate authentication step. RCON access is the access control.
2. Option map
| Option | Direction | Contents |
|---|---|---|
RconDataBridge_ProtocolVersion |
Server to client | {version, ops}, the bridge protocol version and the current allowlisted operation names |
RconDataBridge_WorldStats |
Server to client | Cached world telemetry, refreshed roughly once a minute |
RconDataBridge_PlayerStats_<ID> |
Server to client | Cached snapshot for one player, created lazily on first query |
RconDataBridge_Request |
Client to server | Your request mailbox, a single slot rather than a queue |
RconDataBridge_Response |
Server to client | The response to the most recently processed request |
RconDataBridge_LastResponse |
Server to client | Millisecond epoch timestamp of that response |
Every value in this table is base64 encoded, in both directions, with no
exceptions. Decode after reading, encode before writing.
RconDataBridge_LastResponse is base64 of a plain decimal string, for
example MA== decodes to "0", not JSON; decode it the same way, then
parse it as an integer.
Option names use underscores, never dots. Project Zomboid's own
changeoption command validates the option name argument against \w+
(word characters only) before it ever reaches option lookup, so a dotted
name is silently rejected at the command parsing stage, before the bridge
sees it at all.
Why base64 everywhere?
changeoption strips every literal double-quote character from every
argument as part of its own command line parsing. This happens
unconditionally, so no quoting strategy avoids it, and raw JSON can never
survive a changeoption call intact. Base64's alphabet contains no quote
characters, so it survives the round trip untouched. This is a hard
requirement for the client to server Request channel specifically.
It's not strictly required for the server to client options; those are
written directly into the option store rather than going through
changeoption's command parser, so raw JSON would technically survive
there too. They're base64 encoded anyway, deliberately, so a client only
ever needs one rule, "everything is base64," instead of a special case for
one channel.
3. The request/response cycle
There is no push notification and no long poll here; this is a single mailbox slot, not a queue. The cycle is:
- Build a request envelope (see section 4).
- Encode it as compact JSON, with no whitespace, see the note in section 4, then base64.
- Send
changeoption RconDataBridge_Request <base64>. - Poll
showoptions, base64 decodeRconDataBridge_LastResponse, and wait until it changes from whatever it was before your write, or just wait a fixed short delay and check thatRconDataBridge_Response'sidfield matches the id you sent. - Base64 decode
RconDataBridge_Responseand parse it as JSON.
The server checks for a new request roughly once a second by default; see the Poll Interval setting in Configuration. There is no engine event for "an option changed," so this is a genuine poll, not a callback. Responses have consistently shown up within a few seconds of the request being written in practice, but this isn't a guaranteed bound; budget a few seconds of latency and don't assume sub-second turnaround.
Because it's a single mailbox, two requests in flight at once will race: if
you send a second request before the first one's response has been read,
you may only ever see the second response. Send one request, wait for its
response, matched by id, then send the next. This bridge is not designed
for concurrent callers.
4. Request envelope
{"v":1,"id":"unique-request-id","op":"get_player","args":{"id":"alice"}}
| Field | Type | Required | Notes |
|---|---|---|---|
v |
number | yes | Must equal the current protocol version (1) |
id |
string | yes | 1 to 128 characters. Your correlation id, see idempotency |
op |
string | yes | One of the operations in Operations Reference |
args |
object | no | Operation specific arguments; omit or use {} for operations that take none |
The JSON must be compact, with no whitespace anywhere. This isn't a style preference: RCON's command line parser only treats a double-quoted span as one argument, so a payload containing a literal space gets split into multiple arguments and fails to parse as a single value. Since the whole thing is base64 encoded regardless, this only matters for how you serialize the JSON before encoding it; just don't pretty-print it.
Size limit: the decoded JSON must be 4096 bytes or smaller, or the request
is rejected with PAYLOAD_TOO_LARGE (see section 7) before it's even
parsed.
5. Response envelope
Success:
{"v":1,"id":"unique-request-id","ok":true,"data":{}}
Failure:
{"v":1,"id":"unique-request-id","ok":false,"error":{"code":"PLAYER_NOT_FOUND","message":"No registered player matched the supplied id."}}
id always echoes the request's id, letting you confirm you're looking
at the response to your request and not a stale one from before you
polled. data's shape depends on the operation; see Operations
Reference.
A response isn't strictly guaranteed, but you're very unlikely to see the
gap in practice. If an operation's handler throws an unexpected error, a
bug rather than a normal error condition, you get back INTERNAL_ERROR
like any other error response; it doesn't go missing. Implement a
client-side timeout anyway, see the latency note in section 3, rather than
waiting indefinitely for a reply.
6. Idempotency
Repeating the same id within the configured idempotency window (60
seconds by default, see Configuration) replays the
original cached response. The operation does not run again, and any
different args you send along with the repeat are ignored. This exists
for safe retries: you sent a request, didn't see a response in time, and
want to resend without double-triggering a side-effecting operation, such
as save_server; use the same id for that.
For routine, repeated polling, such as checking world stats every few
minutes, generate a fresh id per logical request: a UUID, an incrementing
counter, or anything else that won't collide. Reusing a fixed id like
"poll" for every call means you'll only ever see the very first response
for the whole idempotency window, then a fresh one, then the same pattern
again, which isn't what you want for periodic sampling.
After the window expires, an id is no longer considered a retry; reusing it triggers a completely fresh execution, not an error.
7. Rate limiting and errors
The bridge allows a limited number of requests per rolling time window (10
requests per 10 seconds by default, admin-tunable, see
Configuration), globally rather than per RCON connection,
since RCON requests carry no distinguishable caller identity. Exceeding it
returns RATE_LIMITED rather than queuing or blocking.
| Error code | Meaning |
|---|---|
SCHEMA_ERROR |
Malformed base64 or JSON, or the envelope is missing or misshaped |
PAYLOAD_TOO_LARGE |
Decoded request exceeds the size limit (4096 bytes) |
OP_NOT_ALLOWED |
op isn't a recognized operation, see Operations Reference for the current list |
RATE_LIMITED |
Too many requests in the current rate-limit window |
PLAYER_NOT_FOUND |
get_player found no snapshot for the given id |
SAVE_FAILED |
save_server triggered but the underlying save call failed |
INTERNAL_ERROR |
An operation's handler threw an unexpected error, a bug rather than a normal error condition |
A rejected request, any error above, is still logged server-side and still publishes an error response. The only case with no response at all is the unhandled internal error edge case described in section 5.
8. RconDataBridge_ProtocolVersion structure
{"version": 1, "ops": ["get_audit_log", "get_bridge_status", "get_player", "get_world_stats", "list_players", "save_server"]}
Check version against the protocol version you were built against before
assuming anything else in this reference still applies. Check ops before
calling an operation you're not certain is still registered on this build.
This array is generated live from the server's actual allowlist, so it
never lags a build that added or removed an operation, even if this wiki
page temporarily does.
9. Client implementation checklist
- Generate a fresh, unique
idper logical request (section 6); don't hardcode one. - Serialize compact JSON, with no whitespace, before base64 encoding (section 4).
- Decode every
RconDataBridge_*value you read, includingLastResponse(section 2); nothing comes back as raw JSON. - Match responses to requests by
id, not just by "the response changed." A response could still be sitting there from a previous call if you poll too early. - Implement a timeout of a few seconds rather than polling forever; some failure modes produce no response at all (section 5).
- Don't fire a second request before you've read the first one's response. There's one mailbox slot, not a queue (section 3).
- Treat unknown or extra response fields as forward-compatible; don't fail parsing on fields not documented here.
- Accumulate multi-packet RCON responses. A large
showoptionsoutput, this mod's options plus every built-in server option, can exceed a single RCON packet. Project Zomboid's RCON framing chunks large responses across multiple packets sharing the same request id, with no explicit end-of-response marker, so a client that resolves on the first packet will intermittently see truncated or missing option values that are, in fact, present server-side. Accumulate every packet for a given request id, using a short quiet-read timeout or a trailer-command trick such as sending a cheap follow-up command and reading until its response shows up, before treating a response as complete. This is a general RCON client concern, not specific to this mod, but it's the most common cause of "options intermittently missing" reports.