Everything the web UI does, it does through this surface. There is no private
back channel: the bundle the Panel embeds is a client of the same routes below,
which is why the reference is worth keeping honest — it is generated from
internal/panel/api/openapi.yaml,
the contract the Panel ships, rather than written beside it.
Every path below is relative to /api/v1 on the Panel's HTTP listener, which
defaults to port 8080. The Agent's gRPC surface is a different thing entirely
and is not documented here; the browser never reaches it.
How a caller authenticates
POST /auth/login returns a token, and every other route wants it as
Authorization: Bearer <token>. The token is opaque — 32 bytes from
crypto/rand — and the Panel stores only its SHA-256 digest, so the value you
hold is the only copy there is.
Sessions expire. The lifetime is KRAKEN_SESSION_TTL, 24 hours by default, and
POST /auth/logout ends one early. A revoked or expired session stops working
everywhere at once, including on a download already granted a token (below).
Three marks on the rows below are worth reading before you skim them:
- no auth on a route means it takes no session at all. There are two: login, and Agent enrollment, which authenticates with a one-time bootstrap token instead.
- The whole
/setup/*group answers only callers whose source address falls insideKRAKEN_SETUP_ALLOWED_CIDRS— loopback and the private ranges by default. Everything else gets a 403 whatever credentials it carries. - Permissions are checked per route on top of the session. The four built-in roles and what they hold are on the security page; a denial on a server you may not see is a 404, not a 403, so that another user's server is not revealed to exist by the refusal.
Downloading a file
The one flow on this page that does not look like the others. A file download
cannot carry an Authorization header and still be a browser navigation, so it
carries a token instead, and the token is deliberately not a session credential:
POST /servers/{id}/files/download-tokenwith the path set you want. It needsserver.files.read, the same permission the raw route carries.- The Panel mints a grant — single-use, 60 seconds, pinned to one server, one route kind and one exact canonicalised path set, and bound to the user and the session that asked — and hands back the token.
GET /servers/{id}/files/raw?token=…orGET /servers/{id}/files/download?token=…redeems it. The browser streams to disk with its own progress, withContent-Lengthannounced whenever the Agent knew the size.
With token present the Authorization header is not consulted at all, so a
live session cannot rescue an invalid token; with it absent, both routes
authenticate exactly as they always have. Redemption is rate limited at 30 a
minute with a burst of 10, per client — the limit sits on the token branch only,
so an ordinary session-authenticated download is never refused for sharing an
address with somebody probing tokens.
The full argument for why a token in a URL is acceptable here, and the short list of what it deliberately does not protect against, is in SECURITY.md and summarised on Files and SFTP.
Errors
A refusal is JSON — {"error": "…"} — under the status code. Two shapes are
worth knowing in advance:
- A malformed id is a 404, not a 500. Ids are Postgres
uuidcolumns, and a value that cannot be one is indistinguishable from a row that is not there. - A 429 carries
Retry-After, and a refused request does not consume future capacity.
Auth
/auth/loginno authLog in and obtain a session token
Rate limited per source IP (20 a minute, burst 20) — generous enough that a whole team behind one NAT address can sign in normally, tight enough to put a ceiling on scripted guessing. A refusal is a 429 with Retry-After.
- 200AuthenticatedLoginResponse
- 401Missing or invalid credentialsError
- 429Too many login attempts from this address; retry after the Retry-After header says
/auth/logoutInvalidate the current session
- 200Logged out
/auth/change-passwordChange the current user's password
Rotates the caller's password, clears the first-run must-change flag, and issues a fresh session token (the old bearer is invalidated).
current_passwordrequired string · passwordnew_passwordrequired string · password
- 200Password changed; returns a rotated sessionLoginResponse
- 400Invalid requestError
- 401Current password incorrect
Servers
/serversCreate (deploy) a server from a spec
spec_idrequired string · uuidnamerequired stringvariablesobjectmemory_mbintegerMemory to reserve. Omit to take the spec's own figure — its recommended memory, falling back to the minimum. An explicit value must be at least the spec's min_memory_mb: below that floor the game does not boot.node_idstring · uuidPin placement to one node. The scheduler still checks eligibility and reserves; an ineligible pin is a 409 naming the reason rather than a silent placement elsewhere.install_bepinexbooleanHonored only when the spec is bepinex_compatible.pin_buildbooleanPin the server to the build this install pulls: no update pass runs before later starts. Default false — every operator-initiated start/restart re-runs the install script first so the game picks up depot updates.steam_guard_codestringOne-time 2FA code for specs whose install needs an authenticated Steam login. Used for this install only, never persisted.
/servers/{id}Get a server
idpathrequired string · uuid
/servers/{id}Delete a retired server permanently
Only a retired server can be deleted: retire a live one first (POST /servers/{id}/retire), which is what removes its containers and world. The node the server was retired from is told to delete what is left of it and its backup archives — but only where they are its own: the zero-config node-local layout keeps each server's archives in <backup_dir>/<server_id>/, and those are deleted. A configured backup directory, a network share, SFTP and SMB keep every server's archives side by side, where an archive cannot be attributed to one server; those are kept, and note names where. An Agent too old to delete archives keeps them too, and note says so. Then the record and its schedules are deleted.
A node that cannot be reached does not block the delete: the removal, delete_backups included, is recorded on the node as a pending removal (see Node.pending_removals) and the node reconciler finishes it when the node answers; removal_pending is then true. A delete whose removal cannot be recorded is refused. Requires server.delete.
idpathrequired string · uuid
- 200Deleted (the node-side removal may still be pending)PermanentDeleteResult
- 404Resource not foundError
- 409
code: server_not_retired— the server is not retired; retire it first.code: server_restoringorserver_busywhile a restore or a retire holds a live server, andserver_busywhile a revive holds a retired one or its retire is still letting go ("the retire is finishing — retry in a moment"). Nothing was sent to the node.Error - 500The node the server was retired from could not be read, or the removal could not be recorded on it; nothing was deleted.Error
/servers/{id}/retireRetire a server — keep its id, config, schedules and backups; remove its containers and world
What the delete button does. Runs in the background and answers at once: 202 with the server in state retiring and a retire block (the state it came from, a phase moving stopping → backing_up → removing, and the final backup's outcome); poll GET /servers/{id} until the state is retired — or, for an abandoned retire, back to what it was. The retire stops the server, takes a final backup unless final_backup is false and waits for it to be ready (at most 30 minutes), then has the node remove the server's containers and data directory — never its archives — releases its memory and ports, deletes its DNS records and port forwards, and switches its schedules off (they are kept, flagged, and the first install that lands after the retire — the revive's, or a reinstall after a revive whose install failed — switches back on exactly those). The retired server is on no node: node_id is empty, retired_from_node_id names where its archives are, and retired_ports the ports it held.
While the node answers, nothing is removed unless the final backup that was asked for is READY. A final backup that fails, or is still being written after 30 minutes, abandons the retire: nothing is removed, the server goes back to the state it came from (offline if the retire had stopped it) and last_error and retire_note say why, starting "retire abandoned:". A backup that could not be tried (the stop or the backup did not reach the node) is tried again when the removal is due, if the node answers then; only a node unreachable at that moment gets its removal queued without a backup — as a pending removal holding the allocation (see Node.pending_removals) — and retire_note says so ("final backup skipped: node unreachable"). A stop the node answers with a refusal abandons the retire as well, and the reason says the way through: retire again with final_backup false, which does not depend on the stop's result: a refusal does not hold it up, and the removal force-removes the container. A retire whose removal cannot be recorded is abandoned too. While it runs, every writer answers 409 server_busy. Requires server.delete.
idpathrequired string · uuid
final_backupbooleantake a backup after the stop and before the removal
- 202The retire started; the server is retiringServer
- 400Invalid requestError
- 404Resource not foundError
- 409Nothing was started.
code: server_retired— already retired.code: server_busy— installing, or a start, restart, reinstall, retire or revive holds the server.code: server_restoring— a backup restore is running.Error
/servers/{id}/reviveRevive a retired server — place it, install it, optionally restore a backup and start it
Places a retired server on a node again — node_id, else the node it was retired from, else (that node being gone) wherever the scheduler finds room — on the platform it ran on, with its old memory unless memory_mb says otherwise. Its old ports are asked for first and kept where they are free on that node; a port taken since falls back to the spec's default, then the lowest free one. Answers 202 with the server installing; the install pass then runs as a create's does (a failure lands install_failed). When it lands, the backup restore_backup_id is restored through the same job as POST /servers/{id}/backups/{backupId}/restore (restoring, then offline with restore_result), and — when start is true and the restore (if any) succeeded — the server is started as an operator start would start it; a start that is refused or fails lands offline with last_error. The schedules the retire switched off are switched back on once the install lands — by this install, or by a later reinstall when this one fails. Requires server.create.
idpathrequired string · uuid
node_idstringplace it on this node; default the node it was retired frommemory_mbintegerdefault its old memory; must clear the spec's minimumrestore_backup_idstringa ready backup of this server on the node it lands onstartbooleanstart it once the install (and the restore) succeededsteam_guard_codestringone-time 2FA code for an authenticated Steam install
- 202Placed; the install is runningServer
- 400Invalid requestError
- 404No such server (or one this user may not reach), or
code: node_not_found— thenode_idasked for does not exist.Error - 409Nothing was changed.
code: server_not_retired— only a retired server can be revived.code: server_busy— a permanent delete or another revive holds it, or its retire is still letting go ("the retire is finishing — retry in a moment").code: removal_pending— the retire's removal of the old containers and world is still owed to a node, and would delete the revived world when it landed; revive once it has, or dismiss it.code: backup_not_found/backup_not_ready— the backup to restore is not a ready archive on that node.code: spec_missing/platform_missing— the spec, or the platform the server ran on, is gone.code: server_busy— another revive holds it. No code — no node can host it (the scheduler's reason, per node).Error - 503
code: node_unreachable— the backup to restore could not be checked on the node.Error
/servers/{id}/powerPower action (start/stop/restart/kill)
Start and restart re-run the server's install script before launching, so the game picks up depot updates, unless one of these skips it: the server pins its build, the spec sets skip_update_on_start (per spec or per platform), the server's create or reinstall completed within the last 30 minutes (so the pass would only repeat it), or the spec needs an authenticated Steam login and the node has no stored credentials. GET /servers/{id}/settings reports which way the next start will go. That pass is asynchronous: the response is then 202 with state installing (the console streams the install log), and the server continues to running on its own. Where a failed pass lands depends on the phase: a stop (or agent connection) that fails before the install restores the previous state with last_error set and no reinstall gate, a failed install script lands in install_failed and refuses start until a reinstall, and a failed start over a good install lands in offline. Stop/kill, and a start that skips the pass, still answer 200 with the new state.
idpathrequired string · uuid
- 200New lifecycle state
stateServerState
- 202Update pass started; the server is installing and will start when it finishes
stateServerStateupdatingboolean
- 409Start or restart refused: the server is installing, its install failed (reinstall first), it is retired (
code: server_retired, for every action, stop and kill included — it is on no node), a retire or revive holds it (code: server_busy), a backup restore is running (code: server_restoring— stop and kill still reach the agent, and the state staysrestoringuntil the restore ends), the game spec it was built from no longer exists (code: spec_missing; permanent, since a spec's id cannot be recreated), or a setting its spec marksrequiredis empty (code: required_settings_missing, with the keys inmissing_settings). Nothing was attempted and the server's state is unchanged. Stop and kill are never refused for any of these. The node's agent can also refuse a start or restart while an install pass for the server is running on it (code: install_running); retry once it ends.StartRefusal - 500The server's spec could not be read (a store error, not a missing spec), so start and restart were refused rather than run unchecked and nothing was attempted — or the node reported a failure it did not classify (
code: node_error), with its message inerror.Error - 503
code: node_unreachable. Either the panel has no live connection to the hosting node's agent — then nothing was attempted and the server's state is unchanged — or the agent did not answer within the time allowed, and the action may still be completing on the node (a stop still inside its graceful-stop window, say):errorsays which.Error
/servers/{id}/reinstallRe-run the install script once, now
Both the retry for a failed install and the explicit "update now" for a server whose start does not update it (a pinned build, or a spec that opted out of update-on-start). Valid from the stopped states — install_failed, offline, crashed — and refused while the server is installing, starting, running, stopping or restoring (the last with code: server_restoring). Runs asynchronously: the server flips to installing and the install log streams over the console socket.
idpathrequired string · uuid
steam_guard_codestringOne-time 2FA code for specs whose install needs an authenticated Steam login.
- 202Install started
stateServerState
- 409The server is not in a stopped state
/servers/{id}/install-logRead the retained output of the server's most recent install
The install runs on the Agent and is streamed to the Panel, which buffers it in memory: there is no container left to tail once the phase ends. The buffer is kept after the install finishes — success included, since an installer can exit 0 having produced a broken tree — until the server is retired or deleted. A new attempt (a reinstall, or the update pass before a start) does not discard the one it replaces: that attempt is answered as previous, exactly one back, so a reinstall retrying a failed pass keeps the output that says why it failed. The new attempt is opened before the request that starts it is answered, so a read made once the server shows installing already returns that attempt. It does NOT survive a Panel restart; retained is false when nothing is held, which is not the same as an install that printed nothing.
idpathrequired string · uuid
- 200Buffered install output
server_idrequired stringdonerequired booleanthe attempt reached a verdictretainedrequired booleanthis Panel process still holds a bufferstarted_msinteger · int64finished_msinteger · int64linesrequired array of InstallLogLinepreviousrequired objectThe attempt the current one replaced (an InstallAttempt), or null when there is none — never absent. Only one back is kept; a previous attempt is over, sodoneis always true, andfinished_msis absent when it was superseded before it reached a verdict.
/servers/{id}/settingsGet grouped game settings + current values
idpathrequired string · uuid
- 200Settings (groups + values), and what the next start does about updatesServerSettings
/servers/{id}/settingsUpdate game settings values
Send only the settings you are changing. Every key in values is stored on the server verbatim — the panel never compares a value with the spec's default — so a key sent back unchanged becomes the server's own value. A client that GETs the settings and PUTs the whole values map back therefore freezes every value listed in from_spec: it stops following the spec's default and is no longer reported as from the spec. The web UI sends only the fields the operator edited. Sending a blank for a required field that has a spec default hands it back to that default.
idpathrequired string · uuid
valuesobjectvariablesobjectpin_buildbooleanThe server's build pin. Omit to leave it unchanged — an ordinary settings save must not unpin a server.
- 200Applied. The body carries
valuesandfrom_specas the GET returns them (a required field stored blank still yields to the spec's default, and a save of another field keeps it blank on the server), plusvariables,applied,restart_needed,hot_reload,variables_changedandpin_build. - 400The body is invalid, or a value is outside what the spec allows (a read-only setting, a variable that is not editable) — or the settings were saved and the node refused a config file's path as one that can never work (
code: bad_path).Error - 404No such server — or the settings were saved and the node reported a config file's folder missing (
code: not_found).Error - 409The settings were saved, but the node refused the rendered config files:
codeisfile_in_use,node_refusedoralready_exists, anderrorbegins "settings saved but config apply failed:".Error - 500The settings could not be saved — or they were saved and applying the config failed on the Panel (
code: config_apply_failed, a render error) or on the node (code: node_error). When the save itself succeeded,errorbegins "settings saved but config apply failed:".Error - 503The settings were saved, but applying them did not complete (
code: node_unreachable): the node's agent could not be reached, or it did not answer within the time allowed — in which case the config may still have been written on the node.Error
Specs
/specsCreate a spec (accepts JSON or YAML body)
- 201CreatedSpec
/specs/{id}Update a spec (JSON or YAML)
idpathrequired string · uuid
- 200UpdatedSpec
/specs/{id}Delete a spec
idpathrequired string · uuid
- 204Deleted
- 404Resource not foundError
- 409Refused while any server is built from the spec: a server keeps its spec's id for life, and could never start again without it.
errorstringcodestringspec_in_useserversintegerhow many servers use the spec
Nodes
/nodesList nodes
- 200Nodes
nodesarray of Nodepanel_versionstringthe Panel's own build — an agent_version that differs is version skew
/nodes/telemetryLive host vitals for every node
Cpu, memory, disk, network and temperature for each node, as sampled by its Agent and cached by the Panel (refreshed every 5s). Intended to be polled for the node instrument bands.
A node appears only when the Panel holds a recent reading for it: nodes that are unreachable, or whose Agent predates the telemetry RPC, are absent from the map rather than present with zero values. Within a node, each metric group carries a *_known flag — false means the host could not supply that metric (a Windows host has no temperature source, for example) and the value must be rendered as "no data", never as 0.
- 200Telemetry keyed by node id
nodesobject
/nodes/{id}Rename a node, or update its schedulable capacity (total memory, game-port range)
idpathrequired string
namestringDisplay name; servers reference a node by UUID, so renaming never disturbs what is running. Must not be blank.total_memory_mbintegerMust cover memory already reserved by servers on the nodeport_startintegerSet together with port_end; existing allocations are preservedport_endinteger
- 200Updated nodeNode
- 400Invalid capacity values
/nodes/{id}Deregister a node
idpathrequired string
- 204Deleted
/nodes/{id}/agent-updatePush this Panel's embedded agent binary to the node (admin)
Streams the agent build embedded in this Panel (always the Panel's own version) to the node over the mTLS gRPC channel. The agent verifies the checksum, swaps its binary transactionally (keeping the previous build for automatic rollback), and restarts. Dev Panel builds embed no agent binaries and return 503. Containerized agents refuse the push — pull the new image instead.
idpathrequired string
- 200Binary pushed; the agent is restarting into it
from_versionstringto_versionstringbytesintegerrestartingboolean
- 409Agent already at the Panel's version
- 503Agent unreachable, or no embedded agent binary in this Panel build (reason verbatim)
/nodes/{id}/infoLive Agent info (pings the node over mTLS, brings it online)
The Agent's live NodeInfo, including running_servers, managed_containers (with each container's state) and containers_reported, read the same way as on the Node schema.
idpathrequired string
- 200Live node info
- 503The panel could not reach the node's agent, or it did not answer in time (
code: node_unreachable).Error
/nodes/{id}/servers/{serverID}/powerPower action sent straight to one node's agent
A lower-level power path than POST /servers/{id}/power: it forwards the action to the named node's agent and does not run the update pass, re-push the spec or render config. Start and restart are refused on the same grounds as the server-scoped endpoint, with the same bodies.
idpathrequired stringserverIDpathrequired string · uuid
- 200The agent's reported state
statestringthe agent's enum name, e.g. SERVER_STATE_RUNNING
- 400Invalid requestError
- 404No such node (
node not found). Or the server is not reachable through this node's path (code: not_found, "server not found on this node"): no such server, a server that lives on a different node, or one this user may not reach. Those three answer with the same body, so a caller probing other nodes' paths learns neither which servers exist nor where they live. Nothing was sent to any agent. A 404 can also be the agent's own not-found, passed through withcode: not_found.Error - 409Start or restart refused: the server is installing, its install failed, a backup restore is running (
code: server_restoring), its spec no longer exists (code: spec_missing), or a required setting is empty (code: required_settings_missing, with the keys inmissing_settings). Nothing was sent to the agent. Or the agent refused a start or restart because an install pass for the server is running on the node (code: install_running); retry once it ends.StartRefusal - 500The server's spec could not be read, so start and restart were refused — or the node reported a failure it did not classify (
code: node_error), with its message inerror.Error - 503The panel could not reach the node's agent, or the agent did not answer within the time allowed (
code: node_unreachable). A timed-out stop or restart may still be completing on the node.Error
/nodes/{id}/containers/{serverID}Retire an untracked container (data untouched)
Stops and removes the container a node runs for a server id the Panel has no server on that node for — an entry of managed_containers that no server row matches — and has the Agent forget its spec so its watchdog never adopts it again. The server's data directory and backups are left exactly where they are. Requires server.delete and node.manage.
idpathrequired stringserverIDpathrequired stringthe server id from the container'skraken.server_idlabel, as listed in managed_containers
- 204Retired; the container is gone and the node's managed_containers no longer lists it
- 400Malformed server id (code: invalid_server_id)CodedError
- 403Missing server.delete or node.manage (code: forbidden)CodedError
- 404Node not found (code: node_not_found)CodedError
- 409Refused, nothing sent to the node.
code: server_tracked— the Panel tracks a server with this id on this node; delete the server instead.code: removal_pending— a removal is already owed for this id (it may delete the data), so a retire that promised the data stays would be undone by the next replay; let it finish, or dismiss it first.CodedError - 500The node answered but could not remove the container, its reason verbatim — a container engine the Agent cannot reach included ("docker: cannot connect to the daemon: …") —
code: node_error; or the Panel could not read its own records (code: internal).CodedError - 503
code: node_unreachable. The node's Agent could not be reached — nothing was sent — or it did not answer within the time allowed, in which case the removal may still be completing on the node.CodedError
/nodes/{id}/removals/{serverID}Dismiss a pending removal without delivering it
Forgets a removal the node owes — for a node that is gone for good, or a container cleared by hand — and releases the memory and ports it was holding. Nothing is sent to the node: if the container is in fact still running, retire it or remove it on the host. Requires server.delete and node.manage.
idpathrequired stringserverIDpathrequired stringthe server_id of an entry in the node's pending_removals
- 204Dismissed; the record is gone and its allocation released
- 400Malformed server id (code: invalid_server_id)CodedError
- 403Missing server.delete or node.manage (code: forbidden)CodedError
- 404Node not found (code: node_not_found), or no removal is pending for this id (code: removal_not_found)CodedError
- 500The Panel could not read or update the node record (code: internal). Nothing is sent to the node, so no Agent status applies.CodedError
/nodes/{id}/configGet a node's backup/credential config (stored secrets are never echoed)
idpathrequired string
- 200Node configNodeConfigView
- 404Resource not foundError
/nodes/{id}/configUpdate a node's backup/credential config and push it to the Agent
Omitted fields keep their stored value; "" clears one. Saving pushes the config to an online Agent, which verifies the configured remote — the apply_* fields report that outcome, and a failure there is not a save failure.
idpathrequired string
- 200Saved config plus the Agent apply resultNodeConfigView +
appliedbooleanthe config reached the Agentapply_okbooleanthe configured target(s) verifiedapply_detailstring
- 400Unknown backup_target, an unrecognized path token, or both replication mirrors enabled
- 404Resource not foundError
Files
/servers/{id}/filesList files under a path
idpathrequired string · uuidpathquery stringdirectory to list
- 200Directory listing
- 400The path can never work: it escapes the server's data directory, or names the data root where a file or folder is required (
code: bad_path).Error - 404No such server, or one this user may not reach (existence is not revealed) — or the path or backup does not exist on the node (
code: not_found).Error - 409The node refused the operation, and
codesays why:file_in_use(another process holds the file — usually the server's own game, still running),node_refused(the node's filesystem denied it) oralready_exists.errorcarries the node's own words. On an operation that writes the server's files,server_restoringmeans a backup restore holds them andserver_busythat a retire, revive or delete holds the server; nothing was sent to the node. On a retired server every file and backup route but the backup list answersserver_retired(revive it first).Error - 500The node reported a failure it did not classify (
code: node_error);errorcarries its message.Error - 503The panel could not reach the node's agent, or it did not answer in time (
code: node_unreachable).Error
/servers/{id}/files/contentRead a single file's contents
idpathrequired string · uuidpathqueryrequired string
- 200File content (capped)
- 400The path can never work: it escapes the server's data directory, or names the data root where a file or folder is required (
code: bad_path).Error - 404No such server, or one this user may not reach (existence is not revealed) — or the path or backup does not exist on the node (
code: not_found).Error - 409The node refused the operation, and
codesays why:file_in_use(another process holds the file — usually the server's own game, still running),node_refused(the node's filesystem denied it) oralready_exists.errorcarries the node's own words. On an operation that writes the server's files,server_restoringmeans a backup restore holds them andserver_busythat a retire, revive or delete holds the server; nothing was sent to the node. On a retired server every file and backup route but the backup list answersserver_retired(revive it first).Error - 500The node reported a failure it did not classify (
code: node_error);errorcarries its message.Error - 503The panel could not reach the node's agent, or it did not answer in time (
code: node_unreachable).Error
/servers/{id}/files/rawDownload a single file (raw bytes)
Streams the file. Authenticate either with the ordinary session bearer, or with a one-time token from /files/download-token — the token form is what lets a plain download link carry the authorization, so the browser streams to disk instead of buffering the payload in memory. When token is present the Authorization header is not consulted at all, and path must be exactly the path the token was minted for.
The response carries Content-Length when the Agent announced the file's size (an older Agent does not, and then there is none), so a truncated transfer is detectable rather than arriving as a complete download. Accept-Ranges: none goes out either way: a download token is single-use, so a ranged second request could only be refused. Rate limited per source IP (30 a minute, burst 10).
idpathrequired string · uuidpathquery stringThe file to stream. Required on the session-authenticated form; on the token form it may be omitted, since the token already names the path — supplied, it must match that path exactly or the request is refused.tokenquery stringA one-time download token from POST /servers/{id}/files/download-token. Single-use, 60 seconds, bound to one server, one exact path set and the user who minted it. Present, it is the whole authority for the request and no Authorization header is read; absent, the route authenticates as it always has.
- 200Raw file stream
- 400No path was supplied on the session-authenticated form, or the path can never work — it escapes the server's data directory, or names a folder (
code: bad_path) - 401Unknown, expired, already-used or wrong-server/wrong-path download token — or the session that minted it is gone
- 404No such server, or one this user may not reach (existence is not revealed) — or no such file on the node (
code: not_found); the body is a JSON error, never a file - 409The node refused the operation, and
codesays why:file_in_use(another process holds the file — usually the server's own game, still running),node_refused(the node's filesystem denied it) oralready_exists.errorcarries the node's own words. On an operation that writes the server's files,server_restoringmeans a backup restore holds them andserver_busythat a retire, revive or delete holds the server; nothing was sent to the node. On a retired server every file and backup route but the backup list answersserver_retired(revive it first).Error - 429Too many token redemptions from this address; retry after the Retry-After header says
- 500The node reported a failure it did not classify (
code: node_error);errorcarries its message.Error - 503The panel could not reach the node's agent, or it did not answer in time (
code: node_unreachable).Error
/servers/{id}/files/downloadDownload a token's path set as a zip
The GET twin of the zip route, for a plain download link. It carries no body: the paths come from the one-time token, which is also the only thing that authorizes it. Required — without a token there is nothing to stream.
idpathrequired string · uuidtokenqueryrequired stringA one-time download token from POST /servers/{id}/files/download-token. Required here: it is both the authorization and the only source of the paths.
- 200Zip stream
- 400No token was supplied
- 401Unknown, expired, already-used or wrong-server download token — or the session that minted it is gone
- 404No such server, or one this user may not reach (existence is not revealed) — or a path that does not exist on the node (
code: not_found); the body is a JSON error, never a file - 409The node refused the operation, and
codesays why:file_in_use(another process holds the file — usually the server's own game, still running),node_refused(the node's filesystem denied it) oralready_exists.errorcarries the node's own words. On an operation that writes the server's files,server_restoringmeans a backup restore holds them andserver_busythat a retire, revive or delete holds the server; nothing was sent to the node. On a retired server every file and backup route but the backup list answersserver_retired(revive it first).Error - 429Too many token redemptions from this address; retry after the Retry-After header says
- 500The node reported a failure it did not classify (
code: node_error);errorcarries its message.Error - 503The panel could not reach the node's agent, or it did not answer in time (
code: node_unreachable).Error
/servers/{id}/files/downloadDownload selected paths as a zip
idpathrequired string · uuid
pathsarray of string
- 200Zip stream
- 400The path can never work: it escapes the server's data directory, or names the data root where a file or folder is required (
code: bad_path).Error - 404No such server, or one this user may not reach (existence is not revealed) — or the path or backup does not exist on the node (
code: not_found).Error - 409The node refused the operation, and
codesays why:file_in_use(another process holds the file — usually the server's own game, still running),node_refused(the node's filesystem denied it) oralready_exists.errorcarries the node's own words. On an operation that writes the server's files,server_restoringmeans a backup restore holds them andserver_busythat a retire, revive or delete holds the server; nothing was sent to the node. On a retired server every file and backup route but the backup list answersserver_retired(revive it first).Error - 500The node reported a failure it did not classify (
code: node_error);errorcarries its message.Error - 503The panel could not reach the node's agent, or it did not answer in time (
code: node_unreachable).Error
/servers/{id}/files/download-tokenMint a one-time download token
Issues an opaque token that authorizes exactly one download of exactly one path set on this one server, for 60 seconds, on behalf of the calling user. It is redeemed on GET /files/raw or GET /files/download and deleted on first use; the user's permission is re-checked at redemption, so a revoked role takes effect immediately, and so is the minting session, so logging out or having that session revoked kills any token it minted. The token is held in the Panel's memory only, never persisted, and never a session credential. Requires server.files.read — the same permission the raw route carries.
idpathrequired string · uuid
pathstringOne file, for a token redeemable on GET /files/raw.pathsarray of stringA path set, for a token redeemable on GET /files/download.
- 201Token minted
urlstringThe tokenised URL to navigate to.tokenstringkindstringrawzipexpires_atstring · date-timeexpires_in_secondsinteger
- 400No path, both shapes at once, or a path that escapes the tree
- 404No such server, or one this user may not reach (existence is not revealed)
/servers/{id}/files/mkdirCreate a directory
idpathrequired string · uuid
- 201Created
- 400The path can never work: it escapes the server's data directory, or names the data root where a file or folder is required (
code: bad_path).Error - 404No such server, or one this user may not reach (existence is not revealed) — or the path or backup does not exist on the node (
code: not_found).Error - 409The node refused the operation, and
codesays why:file_in_use(another process holds the file — usually the server's own game, still running),node_refused(the node's filesystem denied it) oralready_exists.errorcarries the node's own words. On an operation that writes the server's files,server_restoringmeans a backup restore holds them andserver_busythat a retire, revive or delete holds the server; nothing was sent to the node. On a retired server every file and backup route but the backup list answersserver_retired(revive it first).Error - 500The node reported a failure it did not classify (
code: node_error);errorcarries its message.Error - 503The panel could not reach the node's agent, or it did not answer in time (
code: node_unreachable).Error
/servers/{id}/files/moveMove/rename a path
idpathrequired string · uuid
- 200Moved
- 400The path can never work: it escapes the server's data directory, or names the data root where a file or folder is required (
code: bad_path).Error - 404No such server, or one this user may not reach (existence is not revealed) — or the path or backup does not exist on the node (
code: not_found).Error - 409The node refused the operation, and
codesays why:file_in_use(another process holds the file — usually the server's own game, still running),node_refused(the node's filesystem denied it) oralready_exists.errorcarries the node's own words. On an operation that writes the server's files,server_restoringmeans a backup restore holds them andserver_busythat a retire, revive or delete holds the server; nothing was sent to the node. On a retired server every file and backup route but the backup list answersserver_retired(revive it first).Error - 500The node reported a failure it did not classify (
code: node_error);errorcarries its message.Error - 503The panel could not reach the node's agent, or it did not answer in time (
code: node_unreachable).Error
/servers/{id}/files/copyCopy a path
idpathrequired string · uuid
- 200Copied
- 400The path can never work: it escapes the server's data directory, or names the data root where a file or folder is required (
code: bad_path).Error - 404No such server, or one this user may not reach (existence is not revealed) — or the path or backup does not exist on the node (
code: not_found).Error - 409The node refused the operation, and
codesays why:file_in_use(another process holds the file — usually the server's own game, still running),node_refused(the node's filesystem denied it) oralready_exists.errorcarries the node's own words. On an operation that writes the server's files,server_restoringmeans a backup restore holds them andserver_busythat a retire, revive or delete holds the server; nothing was sent to the node. On a retired server every file and backup route but the backup list answersserver_retired(revive it first).Error - 500The node reported a failure it did not classify (
code: node_error);errorcarries its message.Error - 503The panel could not reach the node's agent, or it did not answer in time (
code: node_unreachable).Error
/servers/{id}/files/writeWrite/overwrite a file
idpathrequired string · uuid
- 201Written
- 400The path can never work: it escapes the server's data directory, or names the data root where a file or folder is required (
code: bad_path).Error - 404No such server, or one this user may not reach (existence is not revealed) — or the path or backup does not exist on the node (
code: not_found).Error - 409The node refused the operation, and
codesays why:file_in_use(another process holds the file — usually the server's own game, still running),node_refused(the node's filesystem denied it) oralready_exists.errorcarries the node's own words. On an operation that writes the server's files,server_restoringmeans a backup restore holds them andserver_busythat a retire, revive or delete holds the server; nothing was sent to the node. On a retired server every file and backup route but the backup list answersserver_retired(revive it first).Error - 500The node reported a failure it did not classify (
code: node_error);errorcarries its message.Error - 503The panel could not reach the node's agent, or it did not answer in time (
code: node_unreachable).Error
/servers/{id}/files/uploadUpload files (multipart)
The request body is capped at 64 MiB plus a megabyte of multipart framing; anything larger is refused outright rather than spooled to the Panel's disk.
idpathrequired string · uuid
- 201Uploaded
- 400The body is not a valid multipart form, names no files, or targets a path that can never work (
code: bad_path) - 404No such server, or one this user may not reach (existence is not revealed) — or the path or backup does not exist on the node (
code: not_found).Error - 409The node refused the operation, and
codesays why:file_in_use(another process holds the file — usually the server's own game, still running),node_refused(the node's filesystem denied it) oralready_exists.errorcarries the node's own words. On an operation that writes the server's files,server_restoringmeans a backup restore holds them andserver_busythat a retire, revive or delete holds the server; nothing was sent to the node. On a retired server every file and backup route but the backup list answersserver_retired(revive it first).Error - 413The upload is larger than the Panel accepts
- 500The node reported a failure it did not classify (
code: node_error);errorcarries its message.Error - 503The panel could not reach the node's agent, or it did not answer in time (
code: node_unreachable).Error
/servers/{id}/files/deleteDelete paths
idpathrequired string · uuid
- 200Deleted
- 400The path can never work: it escapes the server's data directory, or names the data root where a file or folder is required (
code: bad_path).Error - 404No such server, or one this user may not reach (existence is not revealed) — or the path or backup does not exist on the node (
code: not_found).Error - 409The node refused the operation, and
codesays why:file_in_use(another process holds the file — usually the server's own game, still running),node_refused(the node's filesystem denied it) oralready_exists.errorcarries the node's own words. On an operation that writes the server's files,server_restoringmeans a backup restore holds them andserver_busythat a retire, revive or delete holds the server; nothing was sent to the node. On a retired server every file and backup route but the backup list answersserver_retired(revive it first).Error - 500The node reported a failure it did not classify (
code: node_error);errorcarries its message.Error - 503The panel could not reach the node's agent, or it did not answer in time (
code: node_unreachable).Error
Backups
/servers/{id}/backupsList backups
Also answers for a retired server — keeping its archives listable is the point of retiring — from the node it was retired from. When that node no longer exists the answer is 404 not_found: its archives went with it.
idpathrequired string · uuid
- 200Backups
backupsarray of Backup
- 404No such server, or one this user may not reach (existence is not revealed) — or the path or backup does not exist on the node (
code: not_found).Error - 409The node refused the operation, and
codesays why:file_in_use(another process holds the file — usually the server's own game, still running),node_refused(the node's filesystem denied it) oralready_exists.errorcarries the node's own words. On an operation that writes the server's files,server_restoringmeans a backup restore holds them andserver_busythat a retire, revive or delete holds the server; nothing was sent to the node. On a retired server every file and backup route but the backup list answersserver_retired(revive it first).Error - 500The node reported a failure it did not classify (
code: node_error);errorcarries its message.Error - 503The panel could not reach the node's agent, or it did not answer in time (
code: node_unreachable).Error
/servers/{id}/backupsCreate a backup
idpathrequired string · uuid
namestring
- 202Archiving started; poll the list for the backup to reach readyBackup
- 404No such server, or one this user may not reach (existence is not revealed) — or the path or backup does not exist on the node (
code: not_found).Error - 409The node refused the operation, and
codesays why:file_in_use(another process holds the file — usually the server's own game, still running),node_refused(the node's filesystem denied it) oralready_exists.errorcarries the node's own words. On an operation that writes the server's files,server_restoringmeans a backup restore holds them andserver_busythat a retire, revive or delete holds the server; nothing was sent to the node. On a retired server every file and backup route but the backup list answersserver_retired(revive it first).Error - 500The node reported a failure it did not classify (
code: node_error);errorcarries its message.Error - 503The panel could not reach the node's agent, or it did not answer in time (
code: node_unreachable).Error
/servers/{id}/backups/{backupId}/restoreRestore a backup
Only valid while the server is stopped (state offline, crashed or install_failed). Runs asynchronously: the server enters restoring at once, and GET /servers/{id} carries a restore block (the archive, the state to return to, when it began, the phase and the compressed bytes read) until the restore ends. While it runs every writer of the server's tree answers 409 server_restoring — start, restart, reinstall, server delete, settings save, backup create and delete, and every file write, upload, move, copy, mkdir and delete (reads and downloads are not refused) — and scheduled restarts, backups, commands and replication are skipped with the reason in the schedule's last_error. When it ends the server goes back to the state it came from (restore.prev_state), and restore_result records the outcome; a failed restore's reason says whether the files were rolled back. last_error is never written by a restore.
idpathrequired string · uuidbackupIdpathrequired string
- 202Restore started; the server is restoringServer
- 409The server is not stopped, a restore is already running for it (
code: restore_in_progress), a start, restart or reinstall is in flight and still holds the server (code: server_busy— its row may read offline until the agent answers), a retire holds it (code: server_busy, stateretiring), or it is retired (code: server_retired). Nothing was started.Error - 503The panel could not reach the node's agent, or it did not answer in time (
code: node_unreachable).Error
/servers/{id}/backups/{backupId}Delete a backup
idpathrequired string · uuidbackupIdpathrequired string
- 204Deleted
- 404No such server, or one this user may not reach (existence is not revealed) — or the path or backup does not exist on the node (
code: not_found).Error - 409The node refused the operation, and
codesays why:file_in_use(another process holds the file — usually the server's own game, still running),node_refused(the node's filesystem denied it) oralready_exists.errorcarries the node's own words. On an operation that writes the server's files,server_restoringmeans a backup restore holds them andserver_busythat a retire, revive or delete holds the server; nothing was sent to the node. On a retired server every file and backup route but the backup list answersserver_retired(revive it first).Error - 500The node reported a failure it did not classify (
code: node_error);errorcarries its message.Error - 503The panel could not reach the node's agent, or it did not answer in time (
code: node_unreachable).Error
Schedules
/servers/{id}/schedulesList a server's scheduled tasks
idpathrequired string · uuid
- 200Schedules
schedulesarray of ScheduledTask
/servers/{id}/schedulesCreate a scheduled task
idpathrequired string · uuid
- 201CreatedScheduledTask
- 400Invalid requestError
/servers/{id}/schedules/{scheduleId}Update a scheduled task
idpathrequired string · uuidscheduleIdpathrequired string · uuid
- 200UpdatedScheduledTask
/servers/{id}/schedules/{scheduleId}Delete a scheduled task
idpathrequired string · uuidscheduleIdpathrequired string · uuid
- 204Deleted
Users & Roles
/usersList users
- 200Users
/usersCreate a user
- 201Created
/users/{id}Update a user
idpathrequired string · uuid
- 200Updated
/users/{id}Delete a user
idpathrequired string · uuid
- 204Deleted
/users/{id}/passwordReset a user's password
idpathrequired string · uuid
- 200Reset
/rolesList roles
- 200Roles
/permissionsList known permissions
- 200Permissions
Enrollment
/agents/bootstrap-tokensIssue a one-time Agent bootstrap token (admin)
node_namestringaudit-log label only (default "remote-agent") — the node's real name comes from the agent at registrationttl_secondsintegertoken lifetime (default 900)
- 201Token issued
tokenstringnode_namestringexpires_atstring · date-timeca_fingerprintstringfull SHA-256 fingerprint of the Panel CA — embed in the agent's install command as --ca-fingerprint to pin the enrollment
- 503CA signing not configured
/agents/enroll-statusPoll a bootstrap token's lifecycle (pending → redeemed)
Lets the setup wizard show live enrollment progress. Once redeemed, the response includes the enrolling source IP and the hosts the agent baked into its certificate (its reachable addresses) so node registration can be prefilled.
tokenqueryrequired string
- 200Token state
statusstringpendingredeemedexpirednode_namestringipstringhostsarray of stringagent-advertised reachable addresses, IPs firstagent_portintegeragent-reported gRPC port for the registration prefillexpires_atstring · date-timeredeemed_atstring · date-time
/agents/enrollno authEnroll an Agent — exchange a bootstrap token + CSR for a signed cert
Authenticated solely by the one-time bootstrap token (the Agent has no session yet). Returns the signed Agent certificate and the CA cert. Re-enrolling with a fresh token rotates the certificate.
tokenrequired stringcsrrequired stringPEM-encoded certificate signing requestagent_portintegergRPC port the agent will serve on (default 9090) — used to prefill node registration
- 200Signed certificate + CA (PEM)
certificatestringcastring
- 401Invalid/expired bootstrap token
Setup
/setup/statusFirst-run onboarding progress
The whole /setup/* surface answers only requests whose source IP falls inside KRAKEN_SETUP_ALLOWED_CIDRS (default: loopback + private ranges); external sources receive 403 regardless of credentials. setup_complete latches true permanently once onboarding finishes (computed complete once, or explicitly dismissed).
- 200Setup status
admin_must_change_passwordbooleanusing_memorybooleanhas_node_onlinebooleanhas_specbooleanhas_serverbooleansetup_completeboolean
- 403Source IP outside the setup allowlist
/setup/dismissMark first-run onboarding as finished (hides the Setup shortcut permanently)
- 200Dismissed
setup_completeboolean
- 403Source IP outside the setup allowlist
/setup/databaseCurrent datastore target (never returns the password)
- 200Datastore config
using_memorybooleanenv_lockedbooleanmanaged by KRAKEN_DATABASE_URLhoststringportintegeruserstringdbnamestringsslmodestring
/setup/databaseConnect Postgres — create DB if needed, migrate, persist, restart
- 200Saved; the Panel will restart onto Postgres
restartingboolean
- 400Invalid requestError
- 409Database is env-managed (KRAKEN_DATABASE_URL)
- 502Connection / create / migrate failed
/setup/database/testPreflight a Postgres connection
- 200Reachable
okbooleandb_existsbooleancan_create_dbboolean
- 400Invalid requestError
- 502Connection failed
/setup/local-enrollno authIssue a bootstrap token for the co-located Agent (loopback only)
Mints a one-time enrollment token for the local single-host Agent. Gated on a loopback source IP; not reachable off-host.
- 201Token issuedBootstrapToken
- 403Not called from the Panel host
- 503Agent enrollment not configured
/catalog/{id}/importImport a bundled catalog spec (one-click)
idpathrequired string
- 201ImportedSpec
- 404Catalog item not found
- 409A spec with that slug is already imported
Settings
/settingsPanel-global settings status (never returns secrets)
- 200Settings statusSettingsView
/settingsUpdate Panel-global settings (Cloudflare token, UniFi gateway)
cloudflare_api_tokenstringScoped Cloudflare API token; empty string clears itunifi_urlstringUniFi controller base URL, e.g. https://192.168.1.1unifi_api_keystringUniFi OS API key; empty string clears itunifi_sitestringUniFi site (default 'default')
- 200SavedSettingsView
/settings/cloudflare/testVerify the stored Cloudflare token by listing its zones
- 200Reachable zones
zonesarray of string
- 400Cloudflare not configured
- 502Cloudflare API error
/settings/unifi/testVerify the stored UniFi credentials (lists forwards + WAN IP)
- 200Reachable
forward_countintegerwan_ipstring
- 400UniFi not configured
- 502UniFi API error
DNS
/servers/{id}/dnsCurrent DNS assignment + target for a server
idpathrequired string · uuid
- 200DNS state
cloudflare_configuredbooleanunifi_configuredbooleantarget_hoststringexternal/WAN host for DNS + connectlan_hoststringnode LAN IP (port-forward target)portsobjectdnsServerDNSforwardsobject
/servers/{id}/dnsAssign a DNS name (creates A/CNAME + optional SRV in Cloudflare)
idpathrequired string · uuid
namerequired stringFQDN, e.g. play.example.comservicestringSRV service label (e.g. minecraft); omit to skip SRVport_namestringspec port to advertise; defaults to the primary port
/servers/{id}/dnsRemove a server's DNS records
idpathrequired string · uuid
- 200Removed
/servers/{id}/forwards/{portName}Open (create/enable) or close (disable) a UniFi port forward for a server port
idpathrequired string · uuidportNamepathrequired string
openrequired boolean
Audit
/auditList recent audit entries (newest first)
- 200Audit entries
entriesarray of AuditEntryretention_daysintegerhow many days of audit log the Panel keeps (KRAKEN_AUDIT_RETENTION_DAYS); 0 means entries are never pruned
Meta
/versionPanel build version
The Panel's own build. Shown as the version stamp in the UI and compared against each node's agent_version to surface fleet version skew. Authenticated, so an unauthenticated caller can't fingerprint the build.
- 200Build metadata
versionstringcommitstringdatestring
Schemas
AuditEntryidstring · uuidtimestring · date-timeactorstringactor_idstringactionstringmethodstringpathstringtarget_typestringtarget_idstringstatusintegeripstringforwarded_forstringThe raw X-Forwarded-For chain as received. Present only whenipcannot identify anybody — a NAT gateway standing in for every caller, or an address exempted with KRAKEN_RATE_LIMIT_IP_SKIP. Written by the caller and therefore untrusted: forensics, never an input to a decision.
Backupidstringnamestringsizeintegercreated_msinteger · int64statestringpendingreadyfailedarchive lifecycle — backups run asynchronouslyreplicationstring""pendingdonefailedoff-node (SFTP) mirror status; empty when replication isn't configurederrorstringwhy the backup failed (state=failed), or a degraded-capture note on a ready archive; omitted when clean
BootstrapTokentokenstringnode_namestringexpires_atstring · date-time
CatalogItemidstringnamestringslugstringdescriptionstringicon_urlstringbanner_urlstringplatformsarray of stringalready_importedboolean
CodedErrorerrorstringthe reason, for a personcodestringa stable identifier for a client to branch on
DBConnectRequesthostrequired stringportintegeruserrequired stringpasswordstring · passworddbnamestringsslmodestring
ErrorerrorstringThe human-readable reason, as the UI shows it.codestringA stable, machine-readable reason, present on the failures a client may want to branch on — branch on this, never onerror. A failed Agent operation carries one of node_unreachable, not_found, bad_path, file_in_use, node_refused, already_exists, install_running or node_error; a settings save whose config could not be rendered carries config_apply_failed. A backup restore's refusals carry server_restoring (a restore holds the server's files, so the write, start or delete was refused), restore_in_progress (a second restore) or server_busy (a start, restart or reinstall still holds the server). The retire model (#360) adds server_retired (the server is retired: nothing boots it, writes its files or backs it up — revive it first), server_not_retired (revive and permanent delete are only for a retired server), removal_pending (a removal for the server is still owed to a node) and server_busy for a retire or revive in progress.
InstallAttemptOne earlier install attempt, in the shape of the current one without the server-level fields.
donebooleanthe attempt is over — always true for a previous attemptstarted_msinteger · int64finished_msinteger · int64absent when the attempt was superseded before it reached a verdictlinesarray of InstallLogLine
InstallLogLinetsinteger · int64streamstring"install" or "error"textstring
LoginRequestusernamerequired stringpasswordrequired string · password
LoginResponsetokenstringexpires_atstring · date-timeuserUser
Nodeidstringnamestringosstringlinuxwindowswine_enabledbooleanstatusstringonlinepartialofflinecordonedonline = ready for work; partial = the Agent answers but cannot reach its container runtime (see runtime_error) and is not schedulable; offline = the Agent is unreachable; cordoned = reachable but excluded from new placements.runtime_errorstringwhy the container runtime is unreachable (set while status is partial)agent_versionstringAgent build seen on last contact; compare with panel_version on the list responserunning_serversintegerThe AGENT's own count of thekraken.managedcontainers it had running on last contact. Compared against the server rows the Panel placed on this node it is what surfaces a container the Panel has lost track of; it is not derived from those rows. Always the count of LIVE containers (running, paused or restarting — see managed_containers[].state), whatever managed_containers carries.managed_containersarray ofserver_idstringfrom the container's ownkraken.server_idlabel; what the Panel matches against its rowscontainer_namestringthe container's name on the host (e.g.kraken_<server-id>)statestringDocker's state word as the Agent read it, lowercase and passed through: running, exited, created, dead, paused, restarting, removing. Read it by one rule — LIVE (holds memory and ports) = running, paused, restarting; NOT LIVE = created, exited, dead, removing; EMPTY = live (from an Agent that predates the field, which reported live containers only).
kraken.managedgame container the Agent reported, named, with Docker's state for each (the one-shot_installcontainer is never listed). When containers_reported is true the list includes stopped containers, so a reader that means "running" must keep only the live ones by the rule on state. From an 0.54–0.58 Agent (containers_reported false) it holds only running containers and no state. Absent from an Agent older than 0.54.0, which reports only the count, and absent when empty — so without containers_reported a client must fall back to running_servers rather than treat a missing list as "nothing running".containers_reportedbooleanTrue when the Agent's last report listed every managed container with its state, stopped ones included — even an empty list. It is what makes a missing managed_containers mean "no containers on this node" rather than "an Agent too old to say", and so what lets a client say an offline server has no container until it starts. Absent (false) from an Agent that predates it.pending_removalsarray ofserver_idstringdelete_databooleanwhether the operator's delete also removes the world and configdelete_backupsbooleana permanent delete of a retired server: the node also deletes the server's archives, where they are its ownrequested_atstring · date-timeattemptsintegerfailed tries so far, the one made at delete time includedlast_errorstringthe most recent failure, verbatimnext_attemptstring · date-timewhen the reconciler may try again — 20s after the first failure, doubling after each, an hour apart at mostmemory_mbintegerthe deleted server's memory, still allocated on this node until the removal landsportsarray of integerthe deleted server's host ports, still allocated on this node until the removal lands
addressstringpublic_hoststringexternal_ipstringtotal_memory_mbintegerallocated_memory_mbinteger
NodeConfigUpdateWritable node config. Omit a field to leave it unchanged, send "" to clear it.
backup_targetstring""localsharesftpsmbbackup_dirstringsftp_hoststringsftp_userstringsftp_passwordstring · passwordsftp_private_keystring · passwordfull PEM, newlines includedsftp_base_pathstringsftp_known_host_keystringreplicate_to_sftpbooleansmb_hoststringsmb_sharestringsmb_userstringsmb_passwordstring · passwordsmb_domainstringsmb_base_pathstringreplicate_to_smbbooleansteam_usernamestringsteam_passwordstring · password
NodeConfigViewWhere a node stores backups, plus the credentials the Panel injects into installs. Secrets are write-only: a stored one surfaces as a *_configured flag, never as its value.
backup_targetstringlocalsharesftpsmbshare = a network share mounted on the host; smb = an SMB server the Agent dials itself with the credentials below (no host mount, so it works from a service account)backup_dirstringnode-local (or mounted-share) archive directory; supports {{SLUG}}sftp_hoststringhost:port (default port 22)sftp_userstringsftp_password_configuredbooleansftp_key_configuredbooleansftp_base_pathstringremote archive directory; supports {{SLUG}}sftp_known_host_keystringpinned SSH host key in authorized_keys format; blank = trust-on-usereplicate_to_sftpbooleansmb_hoststringhost[:port] (default port 445)smb_sharestringshare name only, e.g. gamessmb_userstringsmb_password_configuredbooleansmb_domainstringoptional NTLM domain; blank for a NAS or standalone serversmb_base_pathstringdirectory inside the share, share-relative; supports {{SLUG}}replicate_to_smbbooleanmutually exclusive with replicate_to_sftpsteam_usernamestringsteam_configuredboolean
NodeRegisternamestringblank = adopt the agent's self-reported node id (KRAKEN_NODE_ID)osstringlinuxwindowsblank = adopt the agent's self-reported OSwine_enabledbooleanignored — derived from os (Wine ships in the game image, so every Linux node supports linux-wine)addressrequired stringAgent gRPC host:portpublic_hoststringtotal_memory_mbintegerport_startintegerport_endinteger
NodeTelemetryOne node's host vitals. Rate metrics (cpu, network) are measured by the Agent over its own fixed sampling interval, so they do not skew with how often this endpoint is polled.
ts_unix_msinteger · int64when the Agent sampled this readinguptime_secondsinteger · int64cpu_percentnumber · double0-100 across all cores; meaningful only when cpu_knowncpu_coresintegercpu_knownbooleanfalse until the Agent has taken two samples, or when /proc is unreadablemem_total_mbinteger · int64mem_used_mbinteger · int64physical memory in use — total minus available, so reclaimable cache does not count as usedmem_knownbooleandisk_pathstringthe filesystem measured — the one holding the Agent's data dirdisk_total_mbinteger · int64disk_used_mbinteger · int64disk_knownbooleannet_rx_bpsnumber · doublebytes per second across the host's physical interfacesnet_tx_bpsnumber · doublenet_knownbooleanlink_rtt_msnumber · doublethe Panel's own timing of the gRPC round trip that fetched this reading — no *_known flag, because the entry existing is the proof of the round trip
PermanentDeleteResultnotestringEmpty when everything went. Otherwise: archives a shared backup target kept ("archives on a shared backup target were kept (the network share): …"), an Agent too old to delete archives, or a node that is owed the delete.removal_pendingbooleanthe node could not be reached; it deletes what is left, archives included, when it answers
PowerRequestactionrequired stringstartstoprestartkill
RestoreProgressA running backup restore. Progress is compressed bytes read from the archive against its size; bytes_total is 0 when the size is unknown (an agent too old to report progress, or a target that cannot size the archive), and a client must then show progress as unknown, not as zero.
backup_idstringprev_stateServerStatephasestringopeningextractingapplyingdonerestoringrestoring = an old agent restoring without progressbytes_doneinteger · int64bytes_totalinteger · int64started_atstring · date-timeserver clock
RestoreResultHow the most recent backup restore ended. Kept on the server until the next restore replaces it.
backup_idstringokbooleanerrorstringthe agent's reason, which says whether the files were rolled back; absent when okfinished_atstring · date-timeserver clock
ScheduleInputnamestringactionrequired stringrestartbackupcommandreplicatewhat the task does on each run;restartruns only on a server that is running, starting or crashed (it is skipped on offline, stopping, installing and install_failed, and while a required setting is empty or the spec is gone), never runs the update pass, and records any skip in last_error;replicatemirrors the server's existing backups to the node's configured off-node target (SFTP or SMB)cronrequired string5-field cron expression (min hour dom month dow)commandstringrequired when action=commandenabledboolean
ScheduledTaskidstring · uuidserver_idstring · uuidlast_run_atstring · date-timenext_run_atstring · date-timelast_errorstringdisabled_by_retirebooleanswitched off by its server's retire; the first install that lands after the retire (the revive's, or a reinstall after a revive whose install failed) switches it back on. An operator's own enable or disable clears itcreated_atstring · date-time
Serveridstring · uuidnamestringspec_idstring · uuidnode_idstringkindstringlinux-nativelinux-winewindows-nativestateServerStatevarsobjectsettingsobjectportsobjectmemory_mbintegerdnsServerDNSbepinexbooleandeployed with BepInEx mod supportpin_buildbooleanPinned to the build on disk — the panel skips the install/update pass it otherwise runs before every start or restart.last_errorstringwhy the most recent provisioning attempt failed (a backup restore never writes it)restorePresent only while a backup restore is running (staterestoring). backup_id, prev_state and started_at are stored on the server; the phase and byte counts are the running job's live reading.restore_resultHow the most recent backup restore ended.retirephasestringstoppingbacking_upremovingprev_stateServerStatefinal_backupstringoffrequestedreadyfailedskippedthe final backup so far; off = not asked forfinal_backup_notestringwhy it failed or was skippedfinal_backup_idstringthe archive, once the node has onestarted_atstring · date-timeserver clock
retiring).retired_atstring · date-timewhen the server was retired; only on a retired serverretire_notestringWhat the retire could not do — a final backup skipped ("final backup skipped: node unreachable") or failed, a removal queued for a node that did not answer — or, starting "retire abandoned:", why a retire did not happen. Cleared by the next retire and by a revive.retired_from_node_idstringthe node a retired server left: where its archives are, and where a revive places it by default. node_id is empty while it is retiredretired_portsobjectthe host ports a retired server held (spec port name → port), asked for again by a reviveprovisioned_atstring · date-timeWhen the most recent create or reinstall install pass completed. A start within 30 minutes of it skips the update pass, which would only repeat an install that just ran.created_atstring · date-time
ServerDNSnamestringzone_idstringservicestringport_namestringrecord_idsarray of string
ServerSettingsgroupsarray of objectthe spec's grouped settings schemavaluesobjectThis server's effective values: what the start gate judges and the config files render from. A field the server has no stored value for takes the spec's current default, and so does a required field stored blank (empty or only whitespace) when the spec has a non-blank default for it.from_specarray of stringThe setting keys whose value invaluesis the spec's current default rather than one stored on this server: a field the spec added after the server was created, or a required field stored blank that yields to the spec's default. Every other value is the server's own. Always present, in declared order. The Settings tab marks a required field listed here as coming from the spec.variablesarray of objectthe spec's launch variables with this server's valueshot_reloadbooleanpin_buildbooleanupdates_on_startbooleanFalse when the spec opted out of update-on-start (per spec or per platform), so the build pin is moot. About the spec, not the next start — see next_start_updates.next_start_updatesbooleanWhether the next operator start or restart through the panel would run the update pass right now. Scheduled restarts and the node-scoped power endpoint never run it, and it means nothing while the server is installing or install_failed, where a start is refused.update_skip_reasonstringspecpinnedfresh_installsteam_loginWhy next_start_updates is false; absent when it is true.
ServerStateinstallinginstall_failedofflinestartingrunningstoppingcrashedrestoringretiringretiredSettingsViewcloudflare_configuredbooleanunifi_configuredbooleanunifi_urlstringunifi_sitestring
SpecGame specification (the "egg" equivalent). Large; key fields shown.
idstring · uuidnamestringslugstringversionintegerplatformsarray ofkindstringimagestring
startupcommandstringready_regexstringstoptypestringsignalcommandvaluestring
restarton_crashbooleanmax_retriesinteger
portsarray ofnamestringprotocolstringtcpudpdefaultintegerrequiredboolean
backupincludearray of stringexcludearray of string
**spans any number of path segments) matched against data-dir-relative POSIX paths ("savegame/world.db"). include selects (omit for everything); exclude then filters, so an exclude wins on a conflict. Omit the whole block and the Panel applies its built-in policy: the whole data dir minus an ephemeral-only exclude list. Rejected at save time if a pattern does not compile.
StartRefusalerrorstringthe sentence to show the operatorcodestringrequired_settings_missingspec_missinginstall_runningserver_restoringserver_retiredserver_busyabsent for the install-state refusals; install_running when the node's agent refused because an install pass is running there; server_restoring while a backup restore holds the server; server_retired on a retired server (revive it first); server_busy while a retire or revive holds it (state retiring)missing_settingsarray of stringrequired_settings_missing only: the empty fields' keys
Useridstring · uuidusernamestringemailstringrole_idstringdisabledbooleanmust_change_passwordboolean