Register the games you run and the builds behind them. A game is a node in a tree — Minecraft, then Java Edition, then Forge, then a version — and every setting is inherited down it, so your modpack can sit on top of a Plany-published game without copying anything. Upload a build as an archive or point at one, attach it to a game, and every game server created from that game runs it.
Creates a build.
An UPLOAD build comes back in PENDING_UPLOAD with a signed URL in
source.upload.upload_url. Every other source is usable immediately.
parentstringRequireddisplay_namestringRequiredWhat the artifacts are: a dedicated server, or a playable client.
Required, and immutable. It decides what can run the build: a game server runs only KIND_SERVER builds, and a client build on a game server would install and have nothing to start.
KIND_SERVERKIND_CLIENTinstall_commandsrepeated stringCommands run after the artifacts are in place, in order.
The escape hatch that keeps the sources above simple. Forge, for instance, ships an installer rather than a server: fetch the installer with an archive source, then run it here. Without this, every such game would need its own source type.
namestringIdentifieraccounts/{account}/builds/{build}, or builds/{build} for one Plany
publishes.
Account-scoped rather than project-scoped: a studio building one server binary expects to use it across every project, and forcing a copy per project would mean re-uploading gigabytes to change nothing.
The Plany-published pattern is written down because it is what the seeded
catalogue has always used. It was absent from the resource annotation for
as long as Playpute owned this message, so every tool generated from the
schema believed builds/minecraft-java-1-20-4 — a name in the API's own
seed data — was malformed.
versionstringFree-form version label, e.g. "1.20.4" or "v1.2.5". Plany does not parse it: game versioning is not semver often enough to be worth pretending.
operating_systemOperatingSystemOperating system the build's artifacts target.
OPERATING_SYSTEM_LINUXOPERATING_SYSTEM_WINDOWSlabelsmap<string, string>build_idstringrequest_idstringgrpcurl -H "authorization: Bearer $PLANY_API_KEY" \ -d '{ "parent": "accounts/", "build": { "display_name": "My server", "kind": "KIND_SERVER", "source": {} } }' \ api.plany.cloud:443 plany.game.v1.GameRegistryService/CreateBuild{ "name": "builds/my-build", "display_name": "My server", "kind": "KIND_SERVER", "version": "", "source": { "upload": { "upload_url": "", "upload_url_expire_time": "2026-10-04T12:00:00Z", "object": "", "checksum_sha256": "" }, "install_commands": [ "" ] }, "state": "STATE_PENDING_UPLOAD", "error": {}, "size_bytes": "0", "operating_system": "OPERATING_SYSTEM_LINUX", "labels": { "key": "" }, "system": false, "create_time": "2026-10-04T12:00:00Z", "update_time": "2026-10-04T12:00:00Z", "etag": "W/\"1\""}grpcurl -H "authorization: Bearer $PLANY_API_KEY" \ -d '{ "name": "builds/my-build" }' \ api.plany.cloud:443 plany.game.v1.GameRegistryService/GetBuild{ "name": "builds/my-build", "display_name": "My server", "kind": "KIND_SERVER", "version": "", "source": { "upload": { "upload_url": "", "upload_url_expire_time": "2026-10-04T12:00:00Z", "object": "", "checksum_sha256": "" }, "install_commands": [ "" ] }, "state": "STATE_PENDING_UPLOAD", "error": {}, "size_bytes": "0", "operating_system": "OPERATING_SYSTEM_LINUX", "labels": { "key": "" }, "system": false, "create_time": "2026-10-04T12:00:00Z", "update_time": "2026-10-04T12:00:00Z", "etag": "W/\"1\""}parentstringRequiredpage_sizeint32page_tokenstringfilterstringAIP-160 filter, e.g. state = "READY".
kindKindReturn only server builds, or only client builds.
KIND_SERVERKIND_CLIENTgrpcurl -H "authorization: Bearer $PLANY_API_KEY" \ -d '{ "parent": "accounts/", "page_size": 50 }' \ api.plany.cloud:443 plany.game.v1.GameRegistryService/ListBuilds{ "builds": [ { "name": "builds/my-build", "display_name": "My server", "kind": "KIND_SERVER", "version": "", "source": { "install_commands": [ "" ] }, "state": "STATE_PENDING_UPLOAD", "error": {}, "size_bytes": "0", "operating_system": "OPERATING_SYSTEM_LINUX", "labels": { "key": "" }, "system": false, "create_time": "2026-10-04T12:00:00Z", "update_time": "2026-10-04T12:00:00Z", "etag": "W/\"1\"" } ], "next_page_token": ""}display_namestringRequiredWhat the artifacts are: a dedicated server, or a playable client.
Required, and immutable. It decides what can run the build: a game server runs only KIND_SERVER builds, and a client build on a game server would install and have nothing to start.
KIND_SERVERKIND_CLIENTinstall_commandsrepeated stringCommands run after the artifacts are in place, in order.
The escape hatch that keeps the sources above simple. Forge, for instance, ships an installer rather than a server: fetch the installer with an archive source, then run it here. Without this, every such game would need its own source type.
namestringIdentifieraccounts/{account}/builds/{build}, or builds/{build} for one Plany
publishes.
Account-scoped rather than project-scoped: a studio building one server binary expects to use it across every project, and forcing a copy per project would mean re-uploading gigabytes to change nothing.
The Plany-published pattern is written down because it is what the seeded
catalogue has always used. It was absent from the resource annotation for
as long as Playpute owned this message, so every tool generated from the
schema believed builds/minecraft-java-1-20-4 — a name in the API's own
seed data — was malformed.
versionstringFree-form version label, e.g. "1.20.4" or "v1.2.5". Plany does not parse it: game versioning is not semver often enough to be worth pretending.
operating_systemOperatingSystemOperating system the build's artifacts target.
OPERATING_SYSTEM_LINUXOPERATING_SYSTEM_WINDOWSlabelsmap<string, string>grpcurl -H "authorization: Bearer $PLANY_API_KEY" \ -d '{ "build": { "display_name": "My server", "kind": "KIND_SERVER", "source": {} }, "update_mask": "display_name" }' \ api.plany.cloud:443 plany.game.v1.GameRegistryService/UpdateBuild{ "name": "builds/my-build", "display_name": "My server", "kind": "KIND_SERVER", "version": "", "source": { "upload": { "upload_url": "", "upload_url_expire_time": "2026-10-04T12:00:00Z", "object": "", "checksum_sha256": "" }, "install_commands": [ "" ] }, "state": "STATE_PENDING_UPLOAD", "error": {}, "size_bytes": "0", "operating_system": "OPERATING_SYSTEM_LINUX", "labels": { "key": "" }, "system": false, "create_time": "2026-10-04T12:00:00Z", "update_time": "2026-10-04T12:00:00Z", "etag": "W/\"1\""}namestringRequiredforceboolDelete even if games still reference it. Those games stop being launchable; workloads already running are unaffected, because they pinned their build when they were created.
grpcurl -H "authorization: Bearer $PLANY_API_KEY" \ -d '{ "name": "builds/my-build" }' \ api.plany.cloud:443 plany.game.v1.GameRegistryService/DeleteBuild{}Returns a fresh signed upload URL for an UPLOAD build.
Separate from CreateBuild because signed URLs expire and uploads of this size fail: a customer pushing 8GB over a domestic connection should be able to ask for another URL rather than start again with a new build.
namestringRequiredttlDurationHow long the URL should last. Capped by policy.
checksum_sha256stringHex SHA-256 of the archive the client is about to send.
Signed into the URL when given, which makes storage itself refuse an archive that does not hash to it. That is stronger than comparing the two afterwards: a corrupted upload never lands, so there is no window in which a bad object exists and nothing to unwind.
Declared here rather than only on the build because the file is chosen after the build is created — a client picks it, hashes it, and asks for a URL for that exact archive. It is recorded on the build when the upload is finalized.
grpcurl -H "authorization: Bearer $PLANY_API_KEY" \ -d '{ "name": "builds/my-build" }' \ api.plany.cloud:443 plany.game.v1.GameRegistryService/GetBuildUploadUrl{ "upload_url": "", "expire_time": "2026-10-04T12:00:00Z", "required_headers": { "key": "" }}Marks an upload complete, moving the build towards READY.
Called by the client after the PUT succeeds. Plany could poll storage instead, but a customer who has just finished an hour-long upload should not wait on a sweep to find out.
namestringRequiredchecksum_sha256stringHex SHA-256 of what was uploaded, verified against the stored object.
grpcurl -H "authorization: Bearer $PLANY_API_KEY" \ -d '{ "name": "builds/my-build" }' \ api.plany.cloud:443 plany.game.v1.GameRegistryService/FinalizeBuild{ "name": "builds/my-build", "display_name": "My server", "kind": "KIND_SERVER", "version": "", "source": { "upload": { "upload_url": "", "upload_url_expire_time": "2026-10-04T12:00:00Z", "object": "", "checksum_sha256": "" }, "install_commands": [ "" ] }, "state": "STATE_PENDING_UPLOAD", "error": {}, "size_bytes": "0", "operating_system": "OPERATING_SYSTEM_LINUX", "labels": { "key": "" }, "system": false, "create_time": "2026-10-04T12:00:00Z", "update_time": "2026-10-04T12:00:00Z", "etag": "W/\"1\""}Returns a short-lived URL to download a build's archive from.
Called from inside a running server, as that server's own identity, when it installs its build. Because the URL is issued on request rather than stored, it is always current — and a server whose access has been revoked gets nothing.
namestringRequiredgrpcurl -H "authorization: Bearer $PLANY_API_KEY" \ -d '{ "name": "builds/my-build" }' \ api.plany.cloud:443 plany.game.v1.GameRegistryService/GetBuildDownloadUrl{ "download_url": "", "expire_time": "2026-10-04T12:00:00Z", "checksum_sha256": ""}parentstringRequireddisplay_namestringRequirednamestringIdentifiergames/{game} or accounts/{account}/games/{game}.
descriptionstringparentstringThe game this one refines. Empty for a root such as "Minecraft".
A game may descend from a Plany-published game even when it is a customer's own — that is the point of the field.
versionstringFree-form version label for this node, e.g. "1.20.4".
serverServerSpecHow this game runs as a dedicated server. Deployed by plany.playpute.v1.
Absent on a game nobody hosts — a single-player title a customer streams has no server half, and inventing an empty one would make every picker in Playpute offer a game it cannot run.
buildstringArtifacts the server runs. Must be a KIND_SERVER build.
runtimeGameRuntimeHow to run it. Inherited from ancestors field by field — see
ResolveGame on the service.
clientClientSpecHow this game runs as a playable client. Absent on a game that declares no client.
buildstringArtifacts to install. Must be a KIND_CLIENT build.
launchrepeated stringThe command, as an argv, relative to the root of the unpacked build.
Executed, never handed to a shell. A launch command through cmd /c
would make quoting the customer's problem and a game path containing a
space a support ticket — and it is the difference between running a
program and running whatever a build's name expands to.
working_directorystringWorking directory relative to the build root. Empty means the root.
environmentmap<string, string>Environment for the game, on top of the machine's own.
Not a secret store. This is on a catalogue entry anybody who can read the game can read, and it reaches a process the customer controls — so a licence key belongs in Secrets Manager and a graphics setting belongs here.
labelsmap<string, string>game_idstringrequest_idstringGame.
grpcurl -H "authorization: Bearer $PLANY_API_KEY" \ -d '{ "parent": "accounts/", "game": { "display_name": "My server" } }' \ api.plany.cloud:443 plany.game.v1.GameRegistryService/CreateGame{ "name": "games/my-game", "display_name": "My server", "description": "", "parent": "games/my-game", "version": "", "server": { "build": "builds/my-build", "runtime": { "launch_command": [ "" ], "working_directory": "", "environment": { "key": "" }, "startup_timeout": "300s", "stop_signal": "", "stop_timeout": "300s", "operating_system": "OPERATING_SYSTEM_LINUX", "max_players": 0 } }, "client": { "build": "builds/my-build", "launch": [ "" ], "working_directory": "", "environment": { "key": "" } }, "server_launchable": false, "client_launchable": false, "system": false, "labels": { "key": "" }, "create_time": "2026-10-04T12:00:00Z", "update_time": "2026-10-04T12:00:00Z", "etag": "W/\"1\""}grpcurl -H "authorization: Bearer $PLANY_API_KEY" \ -d '{ "name": "games/my-game" }' \ api.plany.cloud:443 plany.game.v1.GameRegistryService/GetGame{ "name": "games/my-game", "display_name": "My server", "description": "", "parent": "games/my-game", "version": "", "server": { "build": "builds/my-build", "runtime": { "launch_command": [ "" ], "working_directory": "", "environment": { "key": "" }, "startup_timeout": "300s", "stop_signal": "", "stop_timeout": "300s", "operating_system": "OPERATING_SYSTEM_LINUX", "max_players": 0 } }, "client": { "build": "builds/my-build", "launch": [ "" ], "working_directory": "", "environment": { "key": "" } }, "server_launchable": false, "client_launchable": false, "system": false, "labels": { "key": "" }, "create_time": "2026-10-04T12:00:00Z", "update_time": "2026-10-04T12:00:00Z", "etag": "W/\"1\""}Lists games visible to an account: its own, plus every Plany-published one.
Both in one call because that is the list a customer wants to see. Making them ask twice and merge would push the tree's shape into every client.
parentstringRequiredpage_sizeint32page_tokenstringparent_gamestringReturn only the direct children of this game. Empty returns every visible game, which is what a search box wants; set it to walk the tree a level at a time, which is what a picker wants.
launchable_forKindReturn only games whose named half can actually be deployed. KIND_SERVER is the list a game server picker wants. Unspecified returns branch nodes too, which is what a catalogue browser wants.
KIND_SERVERKIND_CLIENTexclude_systemboolLeave out Plany-published games.
grpcurl -H "authorization: Bearer $PLANY_API_KEY" \ -d '{ "parent": "accounts/", "page_size": 50 }' \ api.plany.cloud:443 plany.game.v1.GameRegistryService/ListGames{ "games": [ { "name": "games/my-game", "display_name": "My server", "description": "", "parent": "games/my-game", "version": "", "server": { "build": "builds/my-build" }, "client": { "build": "builds/my-build", "launch": [ "" ], "working_directory": "", "environment": { "key": "" } }, "server_launchable": false, "client_launchable": false, "system": false, "labels": { "key": "" }, "create_time": "2026-10-04T12:00:00Z", "update_time": "2026-10-04T12:00:00Z", "etag": "W/\"1\"" } ], "next_page_token": ""}display_namestringRequirednamestringIdentifiergames/{game} or accounts/{account}/games/{game}.
descriptionstringparentstringThe game this one refines. Empty for a root such as "Minecraft".
A game may descend from a Plany-published game even when it is a customer's own — that is the point of the field.
versionstringFree-form version label for this node, e.g. "1.20.4".
serverServerSpecHow this game runs as a dedicated server. Deployed by plany.playpute.v1.
Absent on a game nobody hosts — a single-player title a customer streams has no server half, and inventing an empty one would make every picker in Playpute offer a game it cannot run.
buildstringArtifacts the server runs. Must be a KIND_SERVER build.
runtimeGameRuntimeHow to run it. Inherited from ancestors field by field — see
ResolveGame on the service.
clientClientSpecHow this game runs as a playable client. Absent on a game that declares no client.
buildstringArtifacts to install. Must be a KIND_CLIENT build.
launchrepeated stringThe command, as an argv, relative to the root of the unpacked build.
Executed, never handed to a shell. A launch command through cmd /c
would make quoting the customer's problem and a game path containing a
space a support ticket — and it is the difference between running a
program and running whatever a build's name expands to.
working_directorystringWorking directory relative to the build root. Empty means the root.
environmentmap<string, string>Environment for the game, on top of the machine's own.
Not a secret store. This is on a catalogue entry anybody who can read the game can read, and it reaches a process the customer controls — so a licence key belongs in Secrets Manager and a graphics setting belongs here.
labelsmap<string, string>Required.
server and client are each replaced wholesale when they are in the
mask, the way plany.playpute.v1.UpdateGameServer treats a message field.
A client showing three of a runtime's fields therefore has to read the
current game and merge, or a form that never displayed config_files
silently deletes them.
Game.
grpcurl -H "authorization: Bearer $PLANY_API_KEY" \ -d '{ "game": { "display_name": "My server" }, "update_mask": "display_name" }' \ api.plany.cloud:443 plany.game.v1.GameRegistryService/UpdateGame{ "name": "games/my-game", "display_name": "My server", "description": "", "parent": "games/my-game", "version": "", "server": { "build": "builds/my-build", "runtime": { "launch_command": [ "" ], "working_directory": "", "environment": { "key": "" }, "startup_timeout": "300s", "stop_signal": "", "stop_timeout": "300s", "operating_system": "OPERATING_SYSTEM_LINUX", "max_players": 0 } }, "client": { "build": "builds/my-build", "launch": [ "" ], "working_directory": "", "environment": { "key": "" } }, "server_launchable": false, "client_launchable": false, "system": false, "labels": { "key": "" }, "create_time": "2026-10-04T12:00:00Z", "update_time": "2026-10-04T12:00:00Z", "etag": "W/\"1\""}namestringRequiredforceboolDelete descendants too. Without it, deleting a game that has children is refused rather than orphaning them.
grpcurl -H "authorization: Bearer $PLANY_API_KEY" \ -d '{ "name": "games/my-game" }' \ api.plany.cloud:443 plany.game.v1.GameRegistryService/DeleteGame{}Returns a game with every inherited field resolved, both halves at once.
This is what a settings form should be rendered from, and what a game server is created from. One call returns both halves, so they always come from the same version of the game.
namestringRequiredgrpcurl -H "authorization: Bearer $PLANY_API_KEY" \ -d '{ "name": "games/my-game" }' \ api.plany.cloud:443 plany.game.v1.GameRegistryService/ResolveGame{ "game": { "name": "games/my-game", "display_name": "My server", "description": "", "parent": "games/my-game", "version": "", "server": { "build": "builds/my-build" }, "client": { "build": "builds/my-build", "launch": [ "" ], "working_directory": "", "environment": { "key": "" } }, "server_launchable": false, "client_launchable": false, "system": false, "labels": { "key": "" }, "create_time": "2026-10-04T12:00:00Z", "update_time": "2026-10-04T12:00:00Z", "etag": "W/\"1\"" }, "server": { "build": "builds/my-build", "runtime": { "launch_command": [ "" ], "working_directory": "", "environment": { "key": "" }, "startup_timeout": "300s", "stop_signal": "", "stop_timeout": "300s", "operating_system": "OPERATING_SYSTEM_LINUX", "max_players": 0 } }, "client": { "build": "builds/my-build", "launch": [ "" ], "working_directory": "", "environment": { "key": "" } }, "server_build": { "name": "builds/my-build", "display_name": "My server", "kind": "KIND_SERVER", "version": "", "source": { "install_commands": [ "" ] }, "state": "STATE_PENDING_UPLOAD", "error": {}, "size_bytes": "0", "operating_system": "OPERATING_SYSTEM_LINUX", "labels": { "key": "" }, "system": false, "create_time": "2026-10-04T12:00:00Z", "update_time": "2026-10-04T12:00:00Z", "etag": "W/\"1\"" }, "client_build": { "name": "builds/my-build", "display_name": "My server", "kind": "KIND_SERVER", "version": "", "source": { "install_commands": [ "" ] }, "state": "STATE_PENDING_UPLOAD", "error": {}, "size_bytes": "0", "operating_system": "OPERATING_SYSTEM_LINUX", "labels": { "key": "" }, "system": false, "create_time": "2026-10-04T12:00:00Z", "update_time": "2026-10-04T12:00:00Z", "etag": "W/\"1\"" }, "inherited_from": [ "" ]}ArchiveSource fetches artifacts from a URL.
For vendors that publish a build directly — Mojang's server jar, a Forge installer, a modpack zip. Nothing is mirrored: the URL is fetched when a workload is provisioned, so the vendor's terms are between them and the customer.
urlstringRequiredchecksum_sha256stringHex SHA-256 of the download. Optional, and the difference between "this is the build I tested" and "this is whatever is at that URL today".
extractboolUnpack the download. False leaves it in place, which is what a bare
server.jar wants.
headersmap<string, string>Input onlyExtra headers, for a vendor that requires a token. Write-only.
{ "url": "", "checksum_sha256": "", "extract": false}BuildSource says where a build's artifacts come from.
upload_urlstringOutput onlyWhere the client PUTs the archive. Output-only and short-lived; call
GetBuildUploadUrl for a fresh one.
objectstringOutput onlyStorage path once uploaded.
checksum_sha256stringHex SHA-256 of the archive, checked on arrival.
Optional, and worth setting: given at presign time it is signed into the URL, so storage itself refuses an archive that does not hash to it. A corrupted upload never lands, which is stronger than comparing afterwards — there is no window in which a bad object exists and nothing to unwind.
app_idint64RequiredbranchstringNon-default branch, e.g. "prerelease". Empty takes the public branch.
branch_passwordstringInput onlyPassword for a private branch. Write-only: it is stored encrypted and never returned.
usernamestringSteam account for downloads that refuse anonymous access. Empty uses
anonymous, which covers most dedicated servers and almost no game
clients — a client build is usually somebody's own licensed copy.
passwordstringInput onlyWrite-only, stored encrypted.
validateboolValidate the install after download. Slower, and worth it on a build that will be copied to many machines.
urlstringRequiredchecksum_sha256stringHex SHA-256 of the download. Optional, and the difference between "this is the build I tested" and "this is whatever is at that URL today".
extractboolUnpack the download. False leaves it in place, which is what a bare
server.jar wants.
headersmap<string, string>Input onlyExtra headers, for a vendor that requires a token. Write-only.
imagestringRequiredThe reference as written, e.g. eu-west-2.plany.cr/my-project/lobby:1.4 or
us-docker.pkg.dev/agones-images/examples/simple-game-server:0.35.
A reference that already names a digest is taken as it is.
digeststringOutput onlysha256:…, resolved when the build was created.
install_commandsrepeated stringCommands run after the artifacts are in place, in order.
The escape hatch that keeps the sources above simple. Forge, for instance, ships an installer rather than a server: fetch the installer with an archive source, then run it here. Without this, every such game would need its own source type.
{ "upload": { "upload_url": "", "upload_url_expire_time": "2026-10-04T12:00:00Z", "object": "", "checksum_sha256": "" }, "install_commands": [ "" ]}ClientSpec is a game's playable-client half.
Much smaller than ServerSpec, deliberately: a dedicated server is configured by whoever runs it — ports, files, settings — while a client is configured by the person playing it, through the game's own menus.
buildstringArtifacts to install. Must be a KIND_CLIENT build.
launchrepeated stringThe command, as an argv, relative to the root of the unpacked build.
Executed, never handed to a shell. A launch command through cmd /c
would make quoting the customer's problem and a game path containing a
space a support ticket — and it is the difference between running a
program and running whatever a build's name expands to.
working_directorystringWorking directory relative to the build root. Empty means the root.
environmentmap<string, string>Environment for the game, on top of the machine's own.
Not a secret store. This is on a catalogue entry anybody who can read the game can read, and it reaches a process the customer controls — so a licence key belongs in Secrets Manager and a graphics setting belongs here.
{ "build": "builds/my-build", "launch": [ "" ], "working_directory": "", "environment": { "key": "" }}ConfigFile is a file written into the build before launch.
pathstringRequiredPath relative to the build root.
contentsstringRequiredContents. ${VAR} is substituted from the runtime environment and the
server's settings, which is how one template serves every server.
modeint32Unix mode, e.g. 0644. Zero takes 0644.
preserve_existingboolLeave an existing file alone rather than overwriting it.
Matters on a restart: server.properties is edited by admins in-game, and
rewriting it every boot would silently discard their changes.
{ "path": "", "contents": "", "mode": 0, "preserve_existing": false}ContainerImageSource is a container image, run as the game server.
image is what the customer wrote, and digest is what it pointed at when
this build was created. Everything that runs the build pulls the digest, so a
build is a promise about bytes: a tag pushed over afterwards changes nothing
already running, and two members of a fleet created an hour apart run the same
image. A customer who pushes a new image makes a new build, which is exactly
what rolling a fleet onto it is.
Plany's own registry, <region>.plany.cr/{project}/{repository}, pulled by the
game server as itself — its identity is granted roles/registry.puller on that
one repository. Or any public registry, pulled anonymously. A private registry
elsewhere needs a credential the platform has nowhere to hold yet, and is
refused when the build is created rather than when a server fails to start.
A container build is READY as soon as it resolves. install_commands are
refused on one: the image is the install, and a command run after the pull
would be an install step every member repeats — which is the cold start the
warm-up capture exists to remove.
imagestringRequiredThe reference as written, e.g. eu-west-2.plany.cr/my-project/lobby:1.4 or
us-docker.pkg.dev/agones-images/examples/simple-game-server:0.35.
A reference that already names a digest is taken as it is.
digeststringOutput onlysha256:…, resolved when the build was created.
{ "image": "", "digest": ""}ContainerRuntime overrides a container image's own entrypoint and command.
Field by field, exactly as Kubernetes and Docker resolve it: command
replaces the image's entrypoint and args replaces its command, and setting
only command keeps the image's command. Anything else would surprise
somebody who has written a pod spec before, and every Autopilot customer has.
commandrepeated stringargsrepeated stringworking_directorystringThe container's working directory, absolute inside the image. Empty keeps
the image's own. Separate from GameRuntime.working_directory, which is a
path relative to an installed build and means nothing inside an image.
{ "command": [ "" ], "args": [ "" ], "working_directory": ""}GameRuntime is everything needed to turn server artifacts into a running server.
Every field is individually inheritable. A child game that sets only
launch_command keeps its parent's ports, resources and config files, which
is what makes "Forge 1.20.4" a three-line definition rather than a copy of
everything Minecraft needs.
launch_commandrepeated stringCommand that starts the server, run in the build's directory.
Run directly with its arguments, not through a shell, so pipes, redirects and && have no special meaning. ${VAR} is substituted from environment first.
working_directorystringWorking directory relative to the build root. Empty means the root.
environmentmap<string, string>portsrepeated PortMappingnamestringRequiredStable identifier, e.g. "game", "rcon", "query". Referenced by connection info on a running server, so it must not change between versions of a game.
portint32RequiredprotocolPortMapping.ProtocolPROTOCOL_TCPPROTOCOL_UDPpublicboolReachable from the internet. False keeps it inside the VM — the right default for RCON, which is a remote shell with a password in front of it.
config_filesrepeated ConfigFileFiles written before launch, e.g. server.properties, eula.txt.
pathstringRequiredPath relative to the build root.
contentsstringRequiredContents. ${VAR} is substituted from the runtime environment and the
server's settings, which is how one template serves every server.
modeint32Unix mode, e.g. 0644. Zero takes 0644.
preserve_existingboolLeave an existing file alone rather than overwriting it.
Matters on a restart: server.properties is edited by admins in-game, and
rewriting it every boot would silently discard their changes.
resourcesResourceRequirementsDefault size of a server running this game. A customer may ask for more.
vcpusint32RequiredNumber of virtual CPUs the workload sees. Required, must be at least 1.
reserved_millisint64CPU time reserved on the host, in millicores (1000 = one full core).
Must be <= vcpus * 1000. Zero means "reserve the full vcpus * 1000", i.e. no oversubscription. Below that, the VM is burstable and the difference is the burst headroom.
limit_millisint64Hard ceiling on host CPU time, in millicores, enforced by the agent via cgroup cpu.max. Zero means the ceiling is vcpus * 1000. Set this below vcpus * 1000 to cap a burstable VM that would otherwise consume all idle capacity on the node.
architectureCpuArchitectureCPU_ARCHITECTURE_X86_64CPU_ARCHITECTURE_ARM64required_featuresrepeated stringCPUID feature flags the workload requires, lowercase as they appear in /proc/cpuinfo — e.g. "avx2", "aes". Placement rejects nodes lacking any of them. Anti-cheat and DRM in game servers occasionally depend on these.
dedicated_coresboolPin each vCPU to a distinct physical core and let no other workload share those cores. Removes noisy-neighbour jitter for latency-sensitive tick loops, at the cost of a dedicated-core price.
isolate_smt_siblingsboolRequire SMT siblings of the pinned cores to stay idle. Only meaningful with dedicated_cores; used for workloads that are sensitive to hyperthread contention or that need side-channel isolation.
size_bytesint64RequiredMemory the workload sees, in bytes. Required.
reserved_bytesint64Memory guaranteed to the workload, in bytes. Must be no more than size_bytes. Zero means all of size_bytes is guaranteed.
Reserving less than size_bytes requires balloon_enabled, so unused memory can be reclaimed when it is needed elsewhere.
balloon_enabledboolAllow memory the workload is not using to be reclaimed. Required when reserved_bytes is less than size_bytes.
hugepagesboolBack the workload's memory with 2 MiB pages. Noticeably faster for memory-heavy servers — large voxel worlds, big entity counts — but can only be placed where such memory is available.
hotplug_max_bytesint64Allow memory to grow to this size while running, without a restart. Zero disables it. Must be at least size_bytes when set.
sharedboolAllow identical memory pages to be shared with other workloads on the same machine. Saves memory when many servers run the same game, at some CPU cost and with weaker isolation between them.
disksrepeated DiskSpecBlock devices, in attachment order. Exactly one must set boot unless the
workload boots from an initramfs alone.
disk_idstringRequiredIdentifier for this disk within the workload, e.g. "root" or "world-data". Must be unique within it.
size_bytesint64Size of the device. Required for a blank disk; for an image or snapshot source, zero means "the source's own size" and any larger value grows the filesystem on first boot.
typeDiskTypeDISK_TYPE_LOCAL_NVMEDISK_TYPE_NETWORK_SSDDISK_TYPE_NETWORK_SSD_HIGH_IOPSread_onlyboolAttach read-only. Several instances may share one read-only volume, which is how a common asset bundle is mounted into many game servers at once.
bootboolMarks the disk the workload boots from. Exactly one disk may set this.
delete_on_instance_deleteboolDelete the underlying storage when the instance is deleted. Always true in effect for LOCAL_NVME.
io_limitsDiskSpec.IoLimitsdirect_ioboolUse direct IO (O_DIRECT), bypassing the host page cache. Lowers memory pressure on the node and makes latency more predictable, at the cost of losing host-side caching.
network_interfacesrepeated NetworkInterfaceSpecinterface_idstringRequiredStable identifier within the instance, e.g. "eth0".
networkstringPrivate network to attach to, as a resource name. Empty means the project's default network in this location.
private_ipv4stringRequested private IPv4 address. Empty allocates one from the subnet.
public_address_modeNetworkInterfaceSpec.PublicAddressModePUBLIC_ADDRESS_MODE_NONEPUBLIC_ADDRESS_MODE_SHARED_IPV4PUBLIC_ADDRESS_MODE_DEDICATED_IPV4PUBLIC_ADDRESS_MODE_IPV6tierNetworkTierNETWORK_TIER_STANDARDNETWORK_TIER_PREMIUMrate_limitsNetworkInterfaceSpec.RateLimitsmac_addressstringThe interface's MAC address. Empty generates one. Pinning it matters for game licences tied to a MAC.
multi_queueboolEnable multi-queue virtio-net with one queue pair per vCPU. Raises packet throughput for servers handling many concurrent clients.
tagsrepeated stringNetwork tags, which firewall rules on this interface's network target.
Per interface rather than per instance because a rule belongs to one network, and an instance with an interface on each of two networks is answerable to two sets of rules. Lowercase letters, digits and hyphens, up to 63 characters each.
deny_inboundboolRefuse every inbound connection on this interface, whatever the network's firewall rules say. Replies to connections the workload opened still arrive.
For an interface that exists so a workload can reach a network rather than be reached on it — a sandbox connected to one of your networks, for example.
no_default_routeboolSend no internet traffic and no DNS through this interface: only the network's own range is reached through it. Without this, a workload with two interfaces could send its internet traffic out of either.
placementPlacementSpeczonesrepeated stringZones the instance may land in, as location ids ("eu-west-2a"). Empty means any zone in the region. More than one entry is a preference list in priority order, not a hard set.
required_node_selectorsrepeated NodeSelectorRequirementHard node requirements. A node must satisfy every entry.
preferred_node_selectorsrepeated NodeSelectorRequirementSoft node preferences. Nodes satisfying more of these score higher, but none of them can make placement fail.
placement_groupstringGroup this workload belongs to, scoped to the project. Workloads sharing a group are spread out or packed together according to policy.
policyPlacementPolicyPLACEMENT_POLICY_PACKPLACEMENT_POLICY_SPREADPLACEMENT_POLICY_STRICT_SPREAD_NODEPLACEMENT_POLICY_STRICT_SPREAD_ZONEcapacity_classstringClass of capacity to draw from. Leave empty to let Plany choose, which is right for almost every workload.
disallow_preemptibleboolRefuse to place onto pre-emptible (spot) capacity. Spot is materially cheaper and fine for a dev server, but wrong for a ranked match in progress.
nodestringPin to one node by resource name. Bypasses scoring entirely; intended for debugging and for rescheduling a VM back onto the node holding its local disk.
schedulingSchedulingSpecon_host_maintenanceSchedulingSpec.OnHostMaintenanceON_HOST_MAINTENANCE_MIGRATEON_HOST_MAINTENANCE_TERMINATErestart_policySchedulingSpec.RestartPolicyRESTART_POLICY_NEVERRESTART_POLICY_ALWAYSRESTART_POLICY_ON_FAILUREpreemptibleboolRun as spot: on spare capacity, at the spot rate, reclaimable with two minutes' notice when an on-demand workload needs the room.
preemption_noticeDurationWarning given before a spot reclaim, readable at /metadata/spot/instance-action on the metadata service. The notice is always two minutes; leave this unset.
max_run_durationDurationStop the instance automatically once it has run this long. Zero means no limit. This is the guard rail that stops a forgotten dev server draining a hobby account's credit overnight.
termination_grace_periodDurationTime between the shutdown signal and a forced stop, so a game server can save its world. Zero uses the default of thirty seconds.
preemptible_fallbackboolWith preemptible: place as an ordinary on-demand workload when no spare
capacity fits, rather than waiting for some. The allocation says which
happened, and it is what the instance is billed at.
startup_timeoutDurationHow long to allow for start-up before declaring the server unhealthy.
Generous by necessity: a modded Minecraft server can take minutes to generate a world on first boot, and killing it halfway leaves a corrupt save.
stop_signalstringSignal used to ask the server to exit, e.g. "SIGTERM".
stop_timeoutDurationTime allowed for a clean shutdown before the process is killed. Game servers write world state on exit; cutting that short loses player progress.
settingsrepeated GameSettingSettings a player-facing UI may expose, e.g. max players or difficulty.
keystringRequiredKey, referenced as ${key} from config files and the launch command.
display_namestringdescriptionstringtypeGameSetting.TypeTYPE_STRINGTYPE_INTTYPE_BOOLTYPE_ENUMdefault_valuestringallowed_valuesrepeated stringPermitted values, for TYPE_ENUM.
min_valueint64Bounds, for TYPE_INT.
max_valueint64requires_restartboolRequires a restart to take effect.
operating_systemOperatingSystemOperating system the launch command expects.
OPERATING_SYSTEM_LINUXOPERATING_SYSTEM_WINDOWSmax_playersint32How many players one server of this game holds.
The game's own declaration, inherited down the catalogue like everything else here — Minecraft says twenty, and a Forge variant of it says twenty unless it says otherwise.
It exists for autoscaling: "keep servers 70% full" needs a denominator, and there was nowhere on the platform that held one. Zero means unknown, and unknown is honoured rather than guessed — a group scaling on a percentage of an invented capacity would scale on a number the customer never gave. Such a group falls back to the terms that need no denominator: waiting connections, and CPU.
Deliberately not read from the game's own settings — max-players in
server.properties is a string a customer edits per server, and a fleet
whose denominator changed per member would produce a different target for
each of them.
sdk_healthSdkHealthHow the platform decides a server has stopped answering, for a game that
uses the Agones SDK's Health().
Absent is off, and that is the right default rather than a gap: a game that
never calls Health() would otherwise be declared unhealthy on a timer and
replaced forever. Agones' defaults (5 seconds, 3 failures) are what the
Autopilot importer writes when a manifest leaves health unset.
period_secondsint32Seconds between the pings a healthy server sends. Defaults to 5.
failure_thresholdint32Consecutive missed periods before the server is unhealthy. Defaults to 3.
initial_delay_secondsint32Seconds after the game starts before a missed ping counts. Defaults to 5.
containerContainerRuntimeOverrides for a container build's entrypoint and arguments.
Only meaningful with a ContainerImageSource build, where launch_command
is not used: a container has an entrypoint and a command of its own, and
Kubernetes' rule for overriding them is not "replace the whole argv". See
ContainerRuntime.
commandrepeated stringargsrepeated stringworking_directorystringThe container's working directory, absolute inside the image. Empty keeps
the image's own. Separate from GameRuntime.working_directory, which is a
path relative to an installed build and means nothing inside an image.
{ "launch_command": [ "" ], "working_directory": "", "environment": { "key": "" }, "ports": [ { "name": "", "port": 0, "protocol": "PROTOCOL_TCP", "public": false } ], "config_files": [ { "path": "", "contents": "", "mode": 0, "preserve_existing": false } ], "resources": { "cpu": { "vcpus": 0, "reserved_millis": "0", "limit_millis": "0", "architecture": "CPU_ARCHITECTURE_X86_64", "required_features": [ "" ], "dedicated_cores": false, "isolate_smt_siblings": false }, "memory": { "size_bytes": "0", "reserved_bytes": "0", "balloon_enabled": false, "hugepages": false, "hotplug_max_bytes": "0", "shared": false }, "disks": [ { "disk_id": "my-disk", "source": { "image": "" }, "size_bytes": "0", "type": "DISK_TYPE_LOCAL_NVME", "read_only": false, "boot": false, "delete_on_instance_delete": false, "io_limits": { "bandwidth_bytes_per_second": "0", "bandwidth_burst_bytes": "0", "iops": "0", "iops_burst": "0" }, "direct_io": false } ], "network_interfaces": [ { "interface_id": "my-interface", "network": "", "private_ipv4": "", "public_address_mode": "PUBLIC_ADDRESS_MODE_NONE", "tier": "NETWORK_TIER_STANDARD", "rate_limits": { "ingress_bits_per_second": "0", "egress_bits_per_second": "0", "ingress_burst_bits": "0", "egress_burst_bits": "0", "ingress_packets_per_second": "0", "egress_packets_per_second": "0" }, "mac_address": "", "multi_queue": false, "tags": [ "" ], "deny_inbound": false, "no_default_route": false } ], "placement": { "zones": [ "" ], "required_node_selectors": [ { "key": "", "operator": "OPERATOR_IN", "values": [ "" ] } ], "preferred_node_selectors": [ { "key": "", "operator": "OPERATOR_IN", "values": [ "" ] } ], "placement_group": "", "policy": "PLACEMENT_POLICY_PACK", "capacity_class": "", "disallow_preemptible": false, "node": "" }, "scheduling": { "on_host_maintenance": "ON_HOST_MAINTENANCE_MIGRATE", "restart_policy": "RESTART_POLICY_NEVER", "preemptible": false, "preemption_notice": "300s", "max_run_duration": "300s", "termination_grace_period": "300s", "preemptible_fallback": false } }, "startup_timeout": "300s", "stop_signal": "", "stop_timeout": "300s", "settings": [ { "key": "", "display_name": "My server", "description": "", "type": "TYPE_STRING", "default_value": "", "allowed_values": [ "" ], "min_value": "0", "max_value": "0", "requires_restart": false } ], "operating_system": "OPERATING_SYSTEM_LINUX", "max_players": 0, "sdk_health": { "period_seconds": 0, "failure_threshold": 0, "initial_delay_seconds": 0 }, "container": { "command": [ "" ], "args": [ "" ], "working_directory": "" }}GameSetting is a knob a server operator can turn.
Declared by the game so a console can render a form for any game without knowing anything about it.
keystringRequiredKey, referenced as ${key} from config files and the launch command.
display_namestringdescriptionstringtypeGameSetting.TypeTYPE_STRINGTYPE_INTTYPE_BOOLTYPE_ENUMdefault_valuestringallowed_valuesrepeated stringPermitted values, for TYPE_ENUM.
min_valueint64Bounds, for TYPE_INT.
max_valueint64requires_restartboolRequires a restart to take effect.
{ "key": "", "display_name": "My server", "description": "", "type": "TYPE_STRING", "default_value": "", "allowed_values": [ "" ], "min_value": "0", "max_value": "0", "requires_restart": false}PortMapping is a port the server listens on.
namestringRequiredStable identifier, e.g. "game", "rcon", "query". Referenced by connection info on a running server, so it must not change between versions of a game.
portint32RequiredprotocolPortMapping.ProtocolPROTOCOL_TCPPROTOCOL_UDPpublicboolReachable from the internet. False keeps it inside the VM — the right default for RCON, which is a remote shell with a password in front of it.
{ "name": "", "port": 0, "protocol": "PROTOCOL_TCP", "public": false}SdkHealth is Agones' GameServer.spec.health, in Plany's vocabulary.
period_secondsint32Seconds between the pings a healthy server sends. Defaults to 5.
failure_thresholdint32Consecutive missed periods before the server is unhealthy. Defaults to 3.
initial_delay_secondsint32Seconds after the game starts before a missed ping counts. Defaults to 5.
{ "period_seconds": 0, "failure_threshold": 0, "initial_delay_seconds": 0}ServerSpec is a game's dedicated-server half.
buildstringArtifacts the server runs. Must be a KIND_SERVER build.
runtimeGameRuntimeHow to run it. Inherited from ancestors field by field — see
ResolveGame on the service.
launch_commandrepeated stringCommand that starts the server, run in the build's directory.
Run directly with its arguments, not through a shell, so pipes, redirects and && have no special meaning. ${VAR} is substituted from environment first.
working_directorystringWorking directory relative to the build root. Empty means the root.
environmentmap<string, string>portsrepeated PortMappingnamestringRequiredStable identifier, e.g. "game", "rcon", "query". Referenced by connection info on a running server, so it must not change between versions of a game.
portint32RequiredprotocolPortMapping.ProtocolPROTOCOL_TCPPROTOCOL_UDPpublicboolReachable from the internet. False keeps it inside the VM — the right default for RCON, which is a remote shell with a password in front of it.
config_filesrepeated ConfigFileFiles written before launch, e.g. server.properties, eula.txt.
pathstringRequiredPath relative to the build root.
contentsstringRequiredContents. ${VAR} is substituted from the runtime environment and the
server's settings, which is how one template serves every server.
modeint32Unix mode, e.g. 0644. Zero takes 0644.
preserve_existingboolLeave an existing file alone rather than overwriting it.
Matters on a restart: server.properties is edited by admins in-game, and
rewriting it every boot would silently discard their changes.
resourcesResourceRequirementsDefault size of a server running this game. A customer may ask for more.
disksrepeated DiskSpecBlock devices, in attachment order. Exactly one must set boot unless the
workload boots from an initramfs alone.
network_interfacesrepeated NetworkInterfaceSpecplacementPlacementSpecschedulingSchedulingSpecstartup_timeoutDurationHow long to allow for start-up before declaring the server unhealthy.
Generous by necessity: a modded Minecraft server can take minutes to generate a world on first boot, and killing it halfway leaves a corrupt save.
stop_signalstringSignal used to ask the server to exit, e.g. "SIGTERM".
stop_timeoutDurationTime allowed for a clean shutdown before the process is killed. Game servers write world state on exit; cutting that short loses player progress.
settingsrepeated GameSettingSettings a player-facing UI may expose, e.g. max players or difficulty.
keystringRequiredKey, referenced as ${key} from config files and the launch command.
display_namestringdescriptionstringtypeGameSetting.TypeTYPE_STRINGTYPE_INTTYPE_BOOLTYPE_ENUMdefault_valuestringallowed_valuesrepeated stringPermitted values, for TYPE_ENUM.
min_valueint64Bounds, for TYPE_INT.
max_valueint64requires_restartboolRequires a restart to take effect.
operating_systemOperatingSystemOperating system the launch command expects.
OPERATING_SYSTEM_LINUXOPERATING_SYSTEM_WINDOWSmax_playersint32How many players one server of this game holds.
The game's own declaration, inherited down the catalogue like everything else here — Minecraft says twenty, and a Forge variant of it says twenty unless it says otherwise.
It exists for autoscaling: "keep servers 70% full" needs a denominator, and there was nowhere on the platform that held one. Zero means unknown, and unknown is honoured rather than guessed — a group scaling on a percentage of an invented capacity would scale on a number the customer never gave. Such a group falls back to the terms that need no denominator: waiting connections, and CPU.
Deliberately not read from the game's own settings — max-players in
server.properties is a string a customer edits per server, and a fleet
whose denominator changed per member would produce a different target for
each of them.
sdk_healthSdkHealthHow the platform decides a server has stopped answering, for a game that
uses the Agones SDK's Health().
Absent is off, and that is the right default rather than a gap: a game that
never calls Health() would otherwise be declared unhealthy on a timer and
replaced forever. Agones' defaults (5 seconds, 3 failures) are what the
Autopilot importer writes when a manifest leaves health unset.
period_secondsint32Seconds between the pings a healthy server sends. Defaults to 5.
failure_thresholdint32Consecutive missed periods before the server is unhealthy. Defaults to 3.
initial_delay_secondsint32Seconds after the game starts before a missed ping counts. Defaults to 5.
containerContainerRuntimeOverrides for a container build's entrypoint and arguments.
Only meaningful with a ContainerImageSource build, where launch_command
is not used: a container has an entrypoint and a command of its own, and
Kubernetes' rule for overriding them is not "replace the whole argv". See
ContainerRuntime.
commandrepeated stringargsrepeated stringworking_directorystringThe container's working directory, absolute inside the image. Empty keeps
the image's own. Separate from GameRuntime.working_directory, which is a
path relative to an installed build and means nothing inside an image.
{ "build": "builds/my-build", "runtime": { "launch_command": [ "" ], "working_directory": "", "environment": { "key": "" }, "ports": [ { "name": "", "port": 0, "protocol": "PROTOCOL_TCP", "public": false } ], "config_files": [ { "path": "", "contents": "", "mode": 0, "preserve_existing": false } ], "resources": { "cpu": { "vcpus": 0, "reserved_millis": "0", "limit_millis": "0", "architecture": "CPU_ARCHITECTURE_X86_64", "required_features": [ "" ], "dedicated_cores": false, "isolate_smt_siblings": false }, "memory": { "size_bytes": "0", "reserved_bytes": "0", "balloon_enabled": false, "hugepages": false, "hotplug_max_bytes": "0", "shared": false }, "disks": [ { "disk_id": "my-disk", "size_bytes": "0", "type": "DISK_TYPE_LOCAL_NVME", "read_only": false, "boot": false, "delete_on_instance_delete": false, "direct_io": false } ], "network_interfaces": [ { "interface_id": "my-interface", "network": "", "private_ipv4": "", "public_address_mode": "PUBLIC_ADDRESS_MODE_NONE", "tier": "NETWORK_TIER_STANDARD", "mac_address": "", "multi_queue": false, "tags": [ "" ], "deny_inbound": false, "no_default_route": false } ], "placement": { "zones": [ "" ], "placement_group": "", "policy": "PLACEMENT_POLICY_PACK", "capacity_class": "", "disallow_preemptible": false, "node": "" }, "scheduling": { "on_host_maintenance": "ON_HOST_MAINTENANCE_MIGRATE", "restart_policy": "RESTART_POLICY_NEVER", "preemptible": false, "preemption_notice": "300s", "max_run_duration": "300s", "termination_grace_period": "300s", "preemptible_fallback": false } }, "startup_timeout": "300s", "stop_signal": "", "stop_timeout": "300s", "settings": [ { "key": "", "display_name": "My server", "description": "", "type": "TYPE_STRING", "default_value": "", "allowed_values": [ "" ], "min_value": "0", "max_value": "0", "requires_restart": false } ], "operating_system": "OPERATING_SYSTEM_LINUX", "max_players": 0, "sdk_health": { "period_seconds": 0, "failure_threshold": 0, "initial_delay_seconds": 0 }, "container": { "command": [ "" ], "args": [ "" ], "working_directory": "" } }}SteamCmdSource installs from Steam.
The simplification the brief asks for: for the large family of Source and Steamworks games, an app id is the entire configuration.
Which app id depends on the build's kind, and that is the whole reason this registry exists. Garry's Mod is 4000 to play and 4020 to host: two apps, one game, and until the two catalogues became one there was nowhere to say so. A KIND_SERVER build takes the dedicated server's id and a KIND_CLIENT build takes the game's, and they hang off the same node in the tree.
app_idint64RequiredbranchstringNon-default branch, e.g. "prerelease". Empty takes the public branch.
branch_passwordstringInput onlyPassword for a private branch. Write-only: it is stored encrypted and never returned.
usernamestringSteam account for downloads that refuse anonymous access. Empty uses
anonymous, which covers most dedicated servers and almost no game
clients — a client build is usually somebody's own licensed copy.
passwordstringInput onlyWrite-only, stored encrypted.
validateboolValidate the install after download. Slower, and worth it on a build that will be copied to many machines.
{ "app_id": "0", "branch": "", "username": "", "validate": false}UploadSource stores artifacts in Plany's object storage.
CreateBuild returns a signed upload URL, and the build stays in
PENDING_UPLOAD until the bytes arrive. This is the only source that costs
storage, and the only one a customer has to actively feed.
upload_urlstringOutput onlyWhere the client PUTs the archive. Output-only and short-lived; call
GetBuildUploadUrl for a fresh one.
objectstringOutput onlyStorage path once uploaded.
checksum_sha256stringHex SHA-256 of the archive, checked on arrival.
Optional, and worth setting: given at presign time it is signed into the URL, so storage itself refuses an archive that does not hash to it. A corrupted upload never lands, which is stronger than comparing afterwards — there is no window in which a bad object exists and nothing to unwind.
{ "upload_url": "", "upload_url_expire_time": "2026-10-04T12:00:00Z", "object": "", "checksum_sha256": ""}CpuSpec describes the processor a workload needs.
Two numbers, deliberately separate. vcpus is how many processors the workload sees. reserved_millis is how much CPU time it is guaranteed. A 4-vCPU game server reserving 2000 millis is guaranteed two cores' worth of time and may burst into idle capacity beyond that — which is what makes cheaper sizes possible.
vcpusint32RequiredNumber of virtual CPUs the workload sees. Required, must be at least 1.
reserved_millisint64CPU time reserved on the host, in millicores (1000 = one full core).
Must be <= vcpus * 1000. Zero means "reserve the full vcpus * 1000", i.e. no oversubscription. Below that, the VM is burstable and the difference is the burst headroom.
limit_millisint64Hard ceiling on host CPU time, in millicores, enforced by the agent via cgroup cpu.max. Zero means the ceiling is vcpus * 1000. Set this below vcpus * 1000 to cap a burstable VM that would otherwise consume all idle capacity on the node.
architectureCpuArchitectureCPU_ARCHITECTURE_X86_64CPU_ARCHITECTURE_ARM64required_featuresrepeated stringCPUID feature flags the workload requires, lowercase as they appear in /proc/cpuinfo — e.g. "avx2", "aes". Placement rejects nodes lacking any of them. Anti-cheat and DRM in game servers occasionally depend on these.
dedicated_coresboolPin each vCPU to a distinct physical core and let no other workload share those cores. Removes noisy-neighbour jitter for latency-sensitive tick loops, at the cost of a dedicated-core price.
isolate_smt_siblingsboolRequire SMT siblings of the pinned cores to stay idle. Only meaningful with dedicated_cores; used for workloads that are sensitive to hyperthread contention or that need side-channel isolation.
{ "vcpus": 0, "reserved_millis": "0", "limit_millis": "0", "architecture": "CPU_ARCHITECTURE_X86_64", "required_features": [ "" ], "dedicated_cores": false, "isolate_smt_siblings": false}DiskSpec describes one disk attached to a workload.
disk_idstringRequiredIdentifier for this disk within the workload, e.g. "root" or "world-data". Must be unique within it.
imagestringOne of sourceBoot from a platform or project image, e.g.
projects/{project}/locations/{location}/images/{image}. The image is
copied (or CoW-cloned where the node supports it) into a fresh disk.
volumestringOne of sourceAttach an existing persistent volume by resource name. The volume must be in the same location and unattached.
snapshotstringOne of sourceRestore from a snapshot resource name.
blankboolOne of sourceCreate an empty, zero-filled disk of size_bytes.
size_bytesint64Size of the device. Required for a blank disk; for an image or snapshot source, zero means "the source's own size" and any larger value grows the filesystem on first boot.
typeDiskTypeDISK_TYPE_LOCAL_NVMEDISK_TYPE_NETWORK_SSDDISK_TYPE_NETWORK_SSD_HIGH_IOPSread_onlyboolAttach read-only. Several instances may share one read-only volume, which is how a common asset bundle is mounted into many game servers at once.
bootboolMarks the disk the workload boots from. Exactly one disk may set this.
delete_on_instance_deleteboolDelete the underlying storage when the instance is deleted. Always true in effect for LOCAL_NVME.
io_limitsDiskSpec.IoLimitsbandwidth_bytes_per_secondint64Sustained bytes/second. Zero means unlimited.
bandwidth_burst_bytesint64Burst allowance in bytes above the sustained rate.
iopsint64Sustained IO operations/second. Zero means unlimited.
iops_burstint64Burst allowance in operations above the sustained rate.
direct_ioboolUse direct IO (O_DIRECT), bypassing the host page cache. Lowers memory pressure on the node and makes latency more predictable, at the cost of losing host-side caching.
{ "disk_id": "my-disk", "source": { "image": "" }, "size_bytes": "0", "type": "DISK_TYPE_LOCAL_NVME", "read_only": false, "boot": false, "delete_on_instance_delete": false, "io_limits": { "bandwidth_bytes_per_second": "0", "bandwidth_burst_bytes": "0", "iops": "0", "iops_burst": "0" }, "direct_io": false}IoLimits caps the disk's throughput.
bandwidth_bytes_per_secondint64Sustained bytes/second. Zero means unlimited.
bandwidth_burst_bytesint64Burst allowance in bytes above the sustained rate.
iopsint64Sustained IO operations/second. Zero means unlimited.
iops_burstint64Burst allowance in operations above the sustained rate.
{ "bandwidth_bytes_per_second": "0", "bandwidth_burst_bytes": "0", "iops": "0", "iops_burst": "0"}Source of the disk's initial contents.
imagestringOne of sourceBoot from a platform or project image, e.g.
projects/{project}/locations/{location}/images/{image}. The image is
copied (or CoW-cloned where the node supports it) into a fresh disk.
volumestringOne of sourceAttach an existing persistent volume by resource name. The volume must be in the same location and unattached.
snapshotstringOne of sourceRestore from a snapshot resource name.
blankboolOne of sourceCreate an empty, zero-filled disk of size_bytes.
{ "image": ""}MemorySpec describes the RAM a workload needs.
size_bytesint64RequiredMemory the workload sees, in bytes. Required.
reserved_bytesint64Memory guaranteed to the workload, in bytes. Must be no more than size_bytes. Zero means all of size_bytes is guaranteed.
Reserving less than size_bytes requires balloon_enabled, so unused memory can be reclaimed when it is needed elsewhere.
balloon_enabledboolAllow memory the workload is not using to be reclaimed. Required when reserved_bytes is less than size_bytes.
hugepagesboolBack the workload's memory with 2 MiB pages. Noticeably faster for memory-heavy servers — large voxel worlds, big entity counts — but can only be placed where such memory is available.
hotplug_max_bytesint64Allow memory to grow to this size while running, without a restart. Zero disables it. Must be at least size_bytes when set.
sharedboolAllow identical memory pages to be shared with other workloads on the same machine. Saves memory when many servers run the same game, at some CPU cost and with weaker isolation between them.
{ "size_bytes": "0", "reserved_bytes": "0", "balloon_enabled": false, "hugepages": false, "hotplug_max_bytes": "0", "shared": false}NetworkInterfaceSpec describes one network interface attached to a workload.
interface_idstringRequiredStable identifier within the instance, e.g. "eth0".
networkstringPrivate network to attach to, as a resource name. Empty means the project's default network in this location.
private_ipv4stringRequested private IPv4 address. Empty allocates one from the subnet.
public_address_modeNetworkInterfaceSpec.PublicAddressModePUBLIC_ADDRESS_MODE_NONEPUBLIC_ADDRESS_MODE_SHARED_IPV4PUBLIC_ADDRESS_MODE_DEDICATED_IPV4PUBLIC_ADDRESS_MODE_IPV6tierNetworkTierNETWORK_TIER_STANDARDNETWORK_TIER_PREMIUMrate_limitsNetworkInterfaceSpec.RateLimitsingress_bits_per_secondint64Sustained bits/second. Zero means unlimited.
egress_bits_per_secondint64ingress_burst_bitsint64Burst allowance in bits above the sustained rate.
egress_burst_bitsint64ingress_packets_per_secondint64Packets/second ceiling, which is what actually protects a node from a small-packet flood. Zero means unlimited.
egress_packets_per_secondint64mac_addressstringThe interface's MAC address. Empty generates one. Pinning it matters for game licences tied to a MAC.
multi_queueboolEnable multi-queue virtio-net with one queue pair per vCPU. Raises packet throughput for servers handling many concurrent clients.
tagsrepeated stringNetwork tags, which firewall rules on this interface's network target.
Per interface rather than per instance because a rule belongs to one network, and an instance with an interface on each of two networks is answerable to two sets of rules. Lowercase letters, digits and hyphens, up to 63 characters each.
deny_inboundboolRefuse every inbound connection on this interface, whatever the network's firewall rules say. Replies to connections the workload opened still arrive.
For an interface that exists so a workload can reach a network rather than be reached on it — a sandbox connected to one of your networks, for example.
no_default_routeboolSend no internet traffic and no DNS through this interface: only the network's own range is reached through it. Without this, a workload with two interfaces could send its internet traffic out of either.
{ "interface_id": "my-interface", "network": "", "private_ipv4": "", "public_address_mode": "PUBLIC_ADDRESS_MODE_NONE", "tier": "NETWORK_TIER_STANDARD", "rate_limits": { "ingress_bits_per_second": "0", "egress_bits_per_second": "0", "ingress_burst_bits": "0", "egress_burst_bits": "0", "ingress_packets_per_second": "0", "egress_packets_per_second": "0" }, "mac_address": "", "multi_queue": false, "tags": [ "" ], "deny_inbound": false, "no_default_route": false}Rate limits applied in both directions by the agent.
ingress_bits_per_secondint64Sustained bits/second. Zero means unlimited.
egress_bits_per_secondint64ingress_burst_bitsint64Burst allowance in bits above the sustained rate.
egress_burst_bitsint64ingress_packets_per_secondint64Packets/second ceiling, which is what actually protects a node from a small-packet flood. Zero means unlimited.
egress_packets_per_secondint64{ "ingress_bits_per_second": "0", "egress_bits_per_second": "0", "ingress_burst_bits": "0", "egress_burst_bits": "0", "ingress_packets_per_second": "0", "egress_packets_per_second": "0"}NodeSelectorRequirement matches against the labels of the machines a workload can run on, with Kubernetes selector semantics.
keystringRequiredOPERATOR_INOPERATOR_NOT_INOPERATOR_EXISTSOPERATOR_DOES_NOT_EXISTOPERATOR_GREATER_THANOPERATOR_LESS_THANvaluesrepeated stringValues to match. Empty for EXISTS and DOES_NOT_EXIST; exactly one entry for GREATER_THAN and LESS_THAN.
{ "key": "", "operator": "OPERATOR_IN", "values": [ "" ]}PlacementSpec constrains which nodes an instance may land on.
Everything here is optional. A caller that supplies none of it gets whatever node satisfies the CPU, memory and disk requirements at the lowest cost, which is the right answer for most workloads.
zonesrepeated stringZones the instance may land in, as location ids ("eu-west-2a"). Empty means any zone in the region. More than one entry is a preference list in priority order, not a hard set.
required_node_selectorsrepeated NodeSelectorRequirementHard node requirements. A node must satisfy every entry.
keystringRequiredOPERATOR_INOPERATOR_NOT_INOPERATOR_EXISTSOPERATOR_DOES_NOT_EXISTOPERATOR_GREATER_THANOPERATOR_LESS_THANvaluesrepeated stringValues to match. Empty for EXISTS and DOES_NOT_EXIST; exactly one entry for GREATER_THAN and LESS_THAN.
preferred_node_selectorsrepeated NodeSelectorRequirementSoft node preferences. Nodes satisfying more of these score higher, but none of them can make placement fail.
keystringRequiredOPERATOR_INOPERATOR_NOT_INOPERATOR_EXISTSOPERATOR_DOES_NOT_EXISTOPERATOR_GREATER_THANOPERATOR_LESS_THANvaluesrepeated stringValues to match. Empty for EXISTS and DOES_NOT_EXIST; exactly one entry for GREATER_THAN and LESS_THAN.
placement_groupstringGroup this workload belongs to, scoped to the project. Workloads sharing a group are spread out or packed together according to policy.
policyPlacementPolicyPLACEMENT_POLICY_PACKPLACEMENT_POLICY_SPREADPLACEMENT_POLICY_STRICT_SPREAD_NODEPLACEMENT_POLICY_STRICT_SPREAD_ZONEcapacity_classstringClass of capacity to draw from. Leave empty to let Plany choose, which is right for almost every workload.
disallow_preemptibleboolRefuse to place onto pre-emptible (spot) capacity. Spot is materially cheaper and fine for a dev server, but wrong for a ranked match in progress.
nodestringPin to one node by resource name. Bypasses scoring entirely; intended for debugging and for rescheduling a VM back onto the node holding its local disk.
{ "zones": [ "" ], "required_node_selectors": [ { "key": "", "operator": "OPERATOR_IN", "values": [ "" ] } ], "preferred_node_selectors": [ { "key": "", "operator": "OPERATOR_IN", "values": [ "" ] } ], "placement_group": "", "policy": "PLACEMENT_POLICY_PACK", "capacity_class": "", "disallow_preemptible": false, "node": ""}SchedulingSpec governs an instance's lifecycle once it is placed: how long it may run, what happens when it goes quiet, and how it reacts to node loss.
on_host_maintenanceSchedulingSpec.OnHostMaintenanceON_HOST_MAINTENANCE_MIGRATEON_HOST_MAINTENANCE_TERMINATErestart_policySchedulingSpec.RestartPolicyRESTART_POLICY_NEVERRESTART_POLICY_ALWAYSRESTART_POLICY_ON_FAILUREpreemptibleboolRun as spot: on spare capacity, at the spot rate, reclaimable with two minutes' notice when an on-demand workload needs the room.
preemption_noticeDurationWarning given before a spot reclaim, readable at /metadata/spot/instance-action on the metadata service. The notice is always two minutes; leave this unset.
max_run_durationDurationStop the instance automatically once it has run this long. Zero means no limit. This is the guard rail that stops a forgotten dev server draining a hobby account's credit overnight.
termination_grace_periodDurationTime between the shutdown signal and a forced stop, so a game server can save its world. Zero uses the default of thirty seconds.
preemptible_fallbackboolWith preemptible: place as an ordinary on-demand workload when no spare
capacity fits, rather than waiting for some. The allocation says which
happened, and it is what the instance is billed at.
{ "on_host_maintenance": "ON_HOST_MAINTENANCE_MIGRATE", "restart_policy": "RESTART_POLICY_NEVER", "preemptible": false, "preemption_notice": "300s", "max_run_duration": "300s", "termination_grace_period": "300s", "preemptible_fallback": false}Build is a set of artifacts. Bytes, and where to get them.
Modelled on the observation that almost nobody wants to upload a zip. Most game servers are already published somewhere — Steam, a vendor CDN — and making customers mirror those into Plany would be work with no benefit and a licensing question attached. So a build declares a source, and only the UPLOAD source stores bytes here at all.
display_namestringRequiredWhat the artifacts are: a dedicated server, or a playable client.
Required, and immutable. It decides what can run the build: a game server runs only KIND_SERVER builds, and a client build on a game server would install and have nothing to start.
KIND_SERVERKIND_CLIENTupload_urlstringOutput onlyWhere the client PUTs the archive. Output-only and short-lived; call
GetBuildUploadUrl for a fresh one.
objectstringOutput onlyStorage path once uploaded.
checksum_sha256stringHex SHA-256 of the archive, checked on arrival.
Optional, and worth setting: given at presign time it is signed into the URL, so storage itself refuses an archive that does not hash to it. A corrupted upload never lands, which is stronger than comparing afterwards — there is no window in which a bad object exists and nothing to unwind.
app_idint64RequiredbranchstringNon-default branch, e.g. "prerelease". Empty takes the public branch.
branch_passwordstringInput onlyPassword for a private branch. Write-only: it is stored encrypted and never returned.
usernamestringSteam account for downloads that refuse anonymous access. Empty uses
anonymous, which covers most dedicated servers and almost no game
clients — a client build is usually somebody's own licensed copy.
passwordstringInput onlyWrite-only, stored encrypted.
validateboolValidate the install after download. Slower, and worth it on a build that will be copied to many machines.
urlstringRequiredchecksum_sha256stringHex SHA-256 of the download. Optional, and the difference between "this is the build I tested" and "this is whatever is at that URL today".
extractboolUnpack the download. False leaves it in place, which is what a bare
server.jar wants.
headersmap<string, string>Input onlyExtra headers, for a vendor that requires a token. Write-only.
imagestringRequiredThe reference as written, e.g. eu-west-2.plany.cr/my-project/lobby:1.4 or
us-docker.pkg.dev/agones-images/examples/simple-game-server:0.35.
A reference that already names a digest is taken as it is.
digeststringOutput onlysha256:…, resolved when the build was created.
install_commandsrepeated stringCommands run after the artifacts are in place, in order.
The escape hatch that keeps the sources above simple. Forge, for instance, ships an installer rather than a server: fetch the installer with an archive source, then run it here. Without this, every such game would need its own source type.
namestringIdentifieraccounts/{account}/builds/{build}, or builds/{build} for one Plany
publishes.
Account-scoped rather than project-scoped: a studio building one server binary expects to use it across every project, and forcing a copy per project would mean re-uploading gigabytes to change nothing.
The Plany-published pattern is written down because it is what the seeded
catalogue has always used. It was absent from the resource annotation for
as long as Playpute owned this message, so every tool generated from the
schema believed builds/minecraft-java-1-20-4 — a name in the API's own
seed data — was malformed.
versionstringFree-form version label, e.g. "1.20.4" or "v1.2.5". Plany does not parse it: game versioning is not semver often enough to be worth pretending.
STATE_PENDING_UPLOADSTATE_PROCESSINGSTATE_READYSTATE_FAILEDSTATE_DELETEDWhy the build failed, when it did.
size_bytesint64Output onlyTotal artifact size once resolved. Zero until known — a SteamCMD build does not know its size until an agent has run the download.
operating_systemOperatingSystemOperating system the build's artifacts target.
OPERATING_SYSTEM_LINUXOPERATING_SYSTEM_WINDOWSlabelsmap<string, string>systemboolOutput onlySet on Plany-published builds, which you may read and use but not modify.
etagstringOutput onlyGame is a catalogue entry, and a node in a tree.
Games contain games. Minecraft is a game; Java Edition is a game inside it; Forge is a game inside that; Forge 1.20.4 is a game inside that. Each level is something someone would name, and only the leaves can actually be run.
The tree is a flat collection with a parent pointer rather than nested resource names, because its depth is not known in advance. Two collections share this type:
games/{game} published by Plany, visible to everyone accounts/{account}/games/{game} your own
Your game may name a Plany game as its parent, which is how "our modpack, on top of Minecraft Forge" is expressed without copying anything.
A game has a server half — what a game server runs — and a client half, the playable game. Either may be absent. Both inherit down the tree independently and field by field: a modpack that changes only the server's launch command keeps everything else its parent declared.
display_namestringRequirednamestringIdentifiergames/{game} or accounts/{account}/games/{game}.
descriptionstringparentstringThe game this one refines. Empty for a root such as "Minecraft".
A game may descend from a Plany-published game even when it is a customer's own — that is the point of the field.
versionstringFree-form version label for this node, e.g. "1.20.4".
serverServerSpecHow this game runs as a dedicated server. Deployed by plany.playpute.v1.
Absent on a game nobody hosts — a single-player title a customer streams has no server half, and inventing an empty one would make every picker in Playpute offer a game it cannot run.
buildstringArtifacts the server runs. Must be a KIND_SERVER build.
runtimeGameRuntimeHow to run it. Inherited from ancestors field by field — see
ResolveGame on the service.
launch_commandrepeated stringCommand that starts the server, run in the build's directory.
Run directly with its arguments, not through a shell, so pipes, redirects and && have no special meaning. ${VAR} is substituted from environment first.
working_directorystringWorking directory relative to the build root. Empty means the root.
environmentmap<string, string>portsrepeated PortMappingconfig_filesrepeated ConfigFileFiles written before launch, e.g. server.properties, eula.txt.
resourcesResourceRequirementsDefault size of a server running this game. A customer may ask for more.
startup_timeoutDurationHow long to allow for start-up before declaring the server unhealthy.
Generous by necessity: a modded Minecraft server can take minutes to generate a world on first boot, and killing it halfway leaves a corrupt save.
stop_signalstringSignal used to ask the server to exit, e.g. "SIGTERM".
stop_timeoutDurationTime allowed for a clean shutdown before the process is killed. Game servers write world state on exit; cutting that short loses player progress.
settingsrepeated GameSettingSettings a player-facing UI may expose, e.g. max players or difficulty.
operating_systemOperatingSystemOperating system the launch command expects.
OPERATING_SYSTEM_LINUXOPERATING_SYSTEM_WINDOWSmax_playersint32How many players one server of this game holds.
The game's own declaration, inherited down the catalogue like everything else here — Minecraft says twenty, and a Forge variant of it says twenty unless it says otherwise.
It exists for autoscaling: "keep servers 70% full" needs a denominator, and there was nowhere on the platform that held one. Zero means unknown, and unknown is honoured rather than guessed — a group scaling on a percentage of an invented capacity would scale on a number the customer never gave. Such a group falls back to the terms that need no denominator: waiting connections, and CPU.
Deliberately not read from the game's own settings — max-players in
server.properties is a string a customer edits per server, and a fleet
whose denominator changed per member would produce a different target for
each of them.
sdk_healthSdkHealthHow the platform decides a server has stopped answering, for a game that
uses the Agones SDK's Health().
Absent is off, and that is the right default rather than a gap: a game that
never calls Health() would otherwise be declared unhealthy on a timer and
replaced forever. Agones' defaults (5 seconds, 3 failures) are what the
Autopilot importer writes when a manifest leaves health unset.
containerContainerRuntimeOverrides for a container build's entrypoint and arguments.
Only meaningful with a ContainerImageSource build, where launch_command
is not used: a container has an entrypoint and a command of its own, and
Kubernetes' rule for overriding them is not "replace the whole argv". See
ContainerRuntime.
clientClientSpecHow this game runs as a playable client. Absent on a game that declares no client.
buildstringArtifacts to install. Must be a KIND_CLIENT build.
launchrepeated stringThe command, as an argv, relative to the root of the unpacked build.
Executed, never handed to a shell. A launch command through cmd /c
would make quoting the customer's problem and a game path containing a
space a support ticket — and it is the difference between running a
program and running whatever a build's name expands to.
working_directorystringWorking directory relative to the build root. Empty means the root.
environmentmap<string, string>Environment for the game, on top of the machine's own.
Not a secret store. This is on a catalogue entry anybody who can read the game can read, and it reaches a process the customer controls — so a licence key belongs in Secrets Manager and a graphics setting belongs here.
server_launchableboolOutput onlyWhether each half can actually be deployed, with inheritance applied.
A branch node — "Minecraft", "Java Edition" — organises the tree and has no build behind it, so asking for a server of one is refused at the API rather than failing at boot.
Two booleans rather than one, because the halves are independent: a game may be hostable without being playable from a client build, and the reverse.
client_launchableboolOutput onlysystemboolOutput onlySet on Plany-published games, which you may read and run but not modify.
labelsmap<string, string>etagstringOutput onlydownload_urlstringexpire_timeTimestampchecksum_sha256stringHex SHA-256 of the archive, so the download can be verified rather than trusted.
upload_urlstringexpire_timeTimestamprequired_headersmap<string, string>Headers the client must send with the PUT.
buildsrepeated Builddisplay_namestringRequiredWhat the artifacts are: a dedicated server, or a playable client.
Required, and immutable. It decides what can run the build: a game server runs only KIND_SERVER builds, and a client build on a game server would install and have nothing to start.
KIND_SERVERKIND_CLIENTinstall_commandsrepeated stringCommands run after the artifacts are in place, in order.
The escape hatch that keeps the sources above simple. Forge, for instance, ships an installer rather than a server: fetch the installer with an archive source, then run it here. Without this, every such game would need its own source type.
namestringIdentifieraccounts/{account}/builds/{build}, or builds/{build} for one Plany
publishes.
Account-scoped rather than project-scoped: a studio building one server binary expects to use it across every project, and forcing a copy per project would mean re-uploading gigabytes to change nothing.
The Plany-published pattern is written down because it is what the seeded
catalogue has always used. It was absent from the resource annotation for
as long as Playpute owned this message, so every tool generated from the
schema believed builds/minecraft-java-1-20-4 — a name in the API's own
seed data — was malformed.
versionstringFree-form version label, e.g. "1.20.4" or "v1.2.5". Plany does not parse it: game versioning is not semver often enough to be worth pretending.
STATE_PENDING_UPLOADSTATE_PROCESSINGSTATE_READYSTATE_FAILEDSTATE_DELETEDWhy the build failed, when it did.
size_bytesint64Output onlyTotal artifact size once resolved. Zero until known — a SteamCMD build does not know its size until an agent has run the download.
operating_systemOperatingSystemOperating system the build's artifacts target.
OPERATING_SYSTEM_LINUXOPERATING_SYSTEM_WINDOWSlabelsmap<string, string>systemboolOutput onlySet on Plany-published builds, which you may read and use but not modify.
etagstringOutput onlynext_page_tokenstringgamesrepeated Gamedisplay_namestringRequirednamestringIdentifiergames/{game} or accounts/{account}/games/{game}.
descriptionstringparentstringThe game this one refines. Empty for a root such as "Minecraft".
A game may descend from a Plany-published game even when it is a customer's own — that is the point of the field.
versionstringFree-form version label for this node, e.g. "1.20.4".
serverServerSpecHow this game runs as a dedicated server. Deployed by plany.playpute.v1.
Absent on a game nobody hosts — a single-player title a customer streams has no server half, and inventing an empty one would make every picker in Playpute offer a game it cannot run.
buildstringArtifacts the server runs. Must be a KIND_SERVER build.
runtimeGameRuntimeHow to run it. Inherited from ancestors field by field — see
ResolveGame on the service.
clientClientSpecHow this game runs as a playable client. Absent on a game that declares no client.
buildstringArtifacts to install. Must be a KIND_CLIENT build.
launchrepeated stringThe command, as an argv, relative to the root of the unpacked build.
Executed, never handed to a shell. A launch command through cmd /c
would make quoting the customer's problem and a game path containing a
space a support ticket — and it is the difference between running a
program and running whatever a build's name expands to.
working_directorystringWorking directory relative to the build root. Empty means the root.
environmentmap<string, string>Environment for the game, on top of the machine's own.
Not a secret store. This is on a catalogue entry anybody who can read the game can read, and it reaches a process the customer controls — so a licence key belongs in Secrets Manager and a graphics setting belongs here.
server_launchableboolOutput onlyWhether each half can actually be deployed, with inheritance applied.
A branch node — "Minecraft", "Java Edition" — organises the tree and has no build behind it, so asking for a server of one is refused at the API rather than failing at boot.
Two booleans rather than one, because the halves are independent: a game may be hostable without being playable from a client build, and the reverse.
client_launchableboolOutput onlysystemboolOutput onlySet on Plany-published games, which you may read and run but not modify.
labelsmap<string, string>etagstringOutput onlynext_page_tokenstringgameGameThe game itself, unresolved, so a caller has its display name and version without a second read.
display_namestringRequirednamestringIdentifiergames/{game} or accounts/{account}/games/{game}.
descriptionstringparentstringThe game this one refines. Empty for a root such as "Minecraft".
A game may descend from a Plany-published game even when it is a customer's own — that is the point of the field.
versionstringFree-form version label for this node, e.g. "1.20.4".
serverServerSpecHow this game runs as a dedicated server. Deployed by plany.playpute.v1.
Absent on a game nobody hosts — a single-player title a customer streams has no server half, and inventing an empty one would make every picker in Playpute offer a game it cannot run.
buildstringArtifacts the server runs. Must be a KIND_SERVER build.
runtimeGameRuntimeHow to run it. Inherited from ancestors field by field — see
ResolveGame on the service.
clientClientSpecHow this game runs as a playable client. Absent on a game that declares no client.
buildstringArtifacts to install. Must be a KIND_CLIENT build.
launchrepeated stringThe command, as an argv, relative to the root of the unpacked build.
Executed, never handed to a shell. A launch command through cmd /c
would make quoting the customer's problem and a game path containing a
space a support ticket — and it is the difference between running a
program and running whatever a build's name expands to.
working_directorystringWorking directory relative to the build root. Empty means the root.
environmentmap<string, string>Environment for the game, on top of the machine's own.
Not a secret store. This is on a catalogue entry anybody who can read the game can read, and it reaches a process the customer controls — so a licence key belongs in Secrets Manager and a graphics setting belongs here.
server_launchableboolOutput onlyWhether each half can actually be deployed, with inheritance applied.
A branch node — "Minecraft", "Java Edition" — organises the tree and has no build behind it, so asking for a server of one is refused at the API rather than failing at boot.
Two booleans rather than one, because the halves are independent: a game may be hostable without being playable from a client build, and the reverse.
client_launchableboolOutput onlysystemboolOutput onlySet on Plany-published games, which you may read and run but not modify.
labelsmap<string, string>etagstringOutput onlyserverServerSpecBoth halves with inheritance applied. Either may be absent, which is what a branch node and a server-only game both look like.
buildstringArtifacts the server runs. Must be a KIND_SERVER build.
runtimeGameRuntimeHow to run it. Inherited from ancestors field by field — see
ResolveGame on the service.
launch_commandrepeated stringCommand that starts the server, run in the build's directory.
Run directly with its arguments, not through a shell, so pipes, redirects and && have no special meaning. ${VAR} is substituted from environment first.
working_directorystringWorking directory relative to the build root. Empty means the root.
environmentmap<string, string>portsrepeated PortMappingconfig_filesrepeated ConfigFileFiles written before launch, e.g. server.properties, eula.txt.
resourcesResourceRequirementsDefault size of a server running this game. A customer may ask for more.
startup_timeoutDurationHow long to allow for start-up before declaring the server unhealthy.
Generous by necessity: a modded Minecraft server can take minutes to generate a world on first boot, and killing it halfway leaves a corrupt save.
stop_signalstringSignal used to ask the server to exit, e.g. "SIGTERM".
stop_timeoutDurationTime allowed for a clean shutdown before the process is killed. Game servers write world state on exit; cutting that short loses player progress.
settingsrepeated GameSettingSettings a player-facing UI may expose, e.g. max players or difficulty.
operating_systemOperatingSystemOperating system the launch command expects.
OPERATING_SYSTEM_LINUXOPERATING_SYSTEM_WINDOWSmax_playersint32How many players one server of this game holds.
The game's own declaration, inherited down the catalogue like everything else here — Minecraft says twenty, and a Forge variant of it says twenty unless it says otherwise.
It exists for autoscaling: "keep servers 70% full" needs a denominator, and there was nowhere on the platform that held one. Zero means unknown, and unknown is honoured rather than guessed — a group scaling on a percentage of an invented capacity would scale on a number the customer never gave. Such a group falls back to the terms that need no denominator: waiting connections, and CPU.
Deliberately not read from the game's own settings — max-players in
server.properties is a string a customer edits per server, and a fleet
whose denominator changed per member would produce a different target for
each of them.
sdk_healthSdkHealthHow the platform decides a server has stopped answering, for a game that
uses the Agones SDK's Health().
Absent is off, and that is the right default rather than a gap: a game that
never calls Health() would otherwise be declared unhealthy on a timer and
replaced forever. Agones' defaults (5 seconds, 3 failures) are what the
Autopilot importer writes when a manifest leaves health unset.
containerContainerRuntimeOverrides for a container build's entrypoint and arguments.
Only meaningful with a ContainerImageSource build, where launch_command
is not used: a container has an entrypoint and a command of its own, and
Kubernetes' rule for overriding them is not "replace the whole argv". See
ContainerRuntime.
clientClientSpecbuildstringArtifacts to install. Must be a KIND_CLIENT build.
launchrepeated stringThe command, as an argv, relative to the root of the unpacked build.
Executed, never handed to a shell. A launch command through cmd /c
would make quoting the customer's problem and a game path containing a
space a support ticket — and it is the difference between running a
program and running whatever a build's name expands to.
working_directorystringWorking directory relative to the build root. Empty means the root.
environmentmap<string, string>Environment for the game, on top of the machine's own.
Not a secret store. This is on a catalogue entry anybody who can read the game can read, and it reaches a process the customer controls — so a licence key belongs in Secrets Manager and a graphics setting belongs here.
server_buildBuildThe builds each half resolved to, in full rather than by name, so you have everything you need from one call.
display_namestringRequiredWhat the artifacts are: a dedicated server, or a playable client.
Required, and immutable. It decides what can run the build: a game server runs only KIND_SERVER builds, and a client build on a game server would install and have nothing to start.
KIND_SERVERKIND_CLIENTinstall_commandsrepeated stringCommands run after the artifacts are in place, in order.
The escape hatch that keeps the sources above simple. Forge, for instance, ships an installer rather than a server: fetch the installer with an archive source, then run it here. Without this, every such game would need its own source type.
namestringIdentifieraccounts/{account}/builds/{build}, or builds/{build} for one Plany
publishes.
Account-scoped rather than project-scoped: a studio building one server binary expects to use it across every project, and forcing a copy per project would mean re-uploading gigabytes to change nothing.
The Plany-published pattern is written down because it is what the seeded
catalogue has always used. It was absent from the resource annotation for
as long as Playpute owned this message, so every tool generated from the
schema believed builds/minecraft-java-1-20-4 — a name in the API's own
seed data — was malformed.
versionstringFree-form version label, e.g. "1.20.4" or "v1.2.5". Plany does not parse it: game versioning is not semver often enough to be worth pretending.
STATE_PENDING_UPLOADSTATE_PROCESSINGSTATE_READYSTATE_FAILEDSTATE_DELETEDWhy the build failed, when it did.
size_bytesint64Output onlyTotal artifact size once resolved. Zero until known — a SteamCMD build does not know its size until an agent has run the download.
operating_systemOperatingSystemOperating system the build's artifacts target.
OPERATING_SYSTEM_LINUXOPERATING_SYSTEM_WINDOWSlabelsmap<string, string>systemboolOutput onlySet on Plany-published builds, which you may read and use but not modify.
etagstringOutput onlyclient_buildBuilddisplay_namestringRequiredWhat the artifacts are: a dedicated server, or a playable client.
Required, and immutable. It decides what can run the build: a game server runs only KIND_SERVER builds, and a client build on a game server would install and have nothing to start.
KIND_SERVERKIND_CLIENTinstall_commandsrepeated stringCommands run after the artifacts are in place, in order.
The escape hatch that keeps the sources above simple. Forge, for instance, ships an installer rather than a server: fetch the installer with an archive source, then run it here. Without this, every such game would need its own source type.
namestringIdentifieraccounts/{account}/builds/{build}, or builds/{build} for one Plany
publishes.
Account-scoped rather than project-scoped: a studio building one server binary expects to use it across every project, and forcing a copy per project would mean re-uploading gigabytes to change nothing.
The Plany-published pattern is written down because it is what the seeded
catalogue has always used. It was absent from the resource annotation for
as long as Playpute owned this message, so every tool generated from the
schema believed builds/minecraft-java-1-20-4 — a name in the API's own
seed data — was malformed.
versionstringFree-form version label, e.g. "1.20.4" or "v1.2.5". Plany does not parse it: game versioning is not semver often enough to be worth pretending.
STATE_PENDING_UPLOADSTATE_PROCESSINGSTATE_READYSTATE_FAILEDSTATE_DELETEDWhy the build failed, when it did.
size_bytesint64Output onlyTotal artifact size once resolved. Zero until known — a SteamCMD build does not know its size until an agent has run the download.
operating_systemOperatingSystemOperating system the build's artifacts target.
OPERATING_SYSTEM_LINUXOPERATING_SYSTEM_WINDOWSlabelsmap<string, string>systemboolOutput onlySet on Plany-published builds, which you may read and use but not modify.
etagstringOutput onlyinherited_fromrepeated stringAncestors consulted, nearest first. Returned so a console can show where a setting came from, which is the difference between a form a customer trusts and one they merely accept.
| Value | Description |
|---|---|
| STATE_UNSPECIFIED | |
| STATE_PENDING_UPLOAD | Created, waiting for artifacts. Only UPLOAD builds sit here; every other source is resolvable the moment it is declared. |
| STATE_PROCESSING | Artifacts are being fetched, unpacked or verified. |
| STATE_READY | Usable by a game. |
| STATE_FAILED | Something went wrong. `error` says what. |
| STATE_DELETED | Superseded or deleted. Retained so a server that still references it can explain itself rather than dangling. |
| Value | Description |
|---|---|
| TYPE_UNSPECIFIED | |
| TYPE_STRING | |
| TYPE_INT | |
| TYPE_BOOL | |
| TYPE_ENUM |
Kind is which half of a game something belongs to.
One enum rather than two, used by Build.kind and by the listing filter that
asks which games are deployable. A second declaration meaning the same thing
is the fault pkg/skus and pkg/guestmeta were both written after: two
names for one fact, and one of them eventually drifts.
| Value | Description |
|---|---|
| KIND_UNSPECIFIED | |
| KIND_SERVER | A dedicated server. Runs headless, listens on ports, and is what `plany.playpute.v1.GameServer` installs. |
| KIND_CLIENT | A playable client: a build with a window and a person in front of it, rather than a dedicated server. |
OperatingSystem is what a build's artifacts were compiled for.
| Value | Description |
|---|---|
| OPERATING_SYSTEM_UNSPECIFIED | |
| OPERATING_SYSTEM_LINUX | |
| OPERATING_SYSTEM_WINDOWS |
| Value | Description |
|---|---|
| PROTOCOL_UNSPECIFIED | |
| PROTOCOL_TCP | |
| PROTOCOL_UDP |
CpuArchitecture is the instruction set a workload requires.
| Value | Description |
|---|---|
| CPU_ARCHITECTURE_UNSPECIFIED | |
| CPU_ARCHITECTURE_X86_64 | |
| CPU_ARCHITECTURE_ARM64 |
DiskType selects the class of backing storage, which determines both the performance envelope and the price.
| Value | Description |
|---|---|
| DISK_TYPE_UNSPECIFIED | |
| DISK_TYPE_LOCAL_NVME | Node-local NVMe. Fastest and cheapest, but the data is gone when the VM moves or the node is reclaimed. Correct for game server working data that is restored from object storage on boot. |
| DISK_TYPE_NETWORK_SSD | Network-attached block storage (EBS gp3 class). Survives node loss and follows the VM when it is rescheduled. |
| DISK_TYPE_NETWORK_SSD_HIGH_IOPS | High-IOPS network block storage (EBS io2 class). For databases and write-heavy persistence layers. |
How the interface obtains a publicly routable address.
| Value | Description |
|---|---|
| PUBLIC_ADDRESS_MODE_UNSPECIFIED | |
| PUBLIC_ADDRESS_MODE_NONE | No public address. Reachable only inside the project's private network. |
| PUBLIC_ADDRESS_MODE_SHARED_IPV4 | A public IPv4 address shared with other workloads, with a dedicated range of ports forwarded to this one. Reachable ports without using a whole address. |
| PUBLIC_ADDRESS_MODE_DEDICATED_IPV4 | An IPv4 address used by this instance alone. |
| PUBLIC_ADDRESS_MODE_IPV6 | A routable IPv6 address from the node's delegated prefix. |
NetworkTier selects the egress path and its price.
| Value | Description |
|---|---|
| NETWORK_TIER_UNSPECIFIED | |
| NETWORK_TIER_STANDARD | Standard shared egress. Cheapest, best-effort. |
| NETWORK_TIER_PREMIUM | Premium egress with DDoS scrubbing and anycast ingress. The default for public-facing game servers. |
| Value | Description |
|---|---|
| OPERATOR_UNSPECIFIED | |
| OPERATOR_IN | |
| OPERATOR_NOT_IN | |
| OPERATOR_EXISTS | |
| OPERATOR_DOES_NOT_EXIST | |
| OPERATOR_GREATER_THAN | Numeric comparison against the label's value, for labels such as `node.plany.cloud/cpu-ghz`. |
| OPERATOR_LESS_THAN |
PlacementPolicy expresses how instances of one logical group spread over failure domains.
| Value | Description |
|---|---|
| PLACEMENT_POLICY_UNSPECIFIED | |
| PLACEMENT_POLICY_PACK | Prefer nodes and zones that already hold members of the group. Packing raises utilisation and lets burst nodes drain back to zero sooner. |
| PLACEMENT_POLICY_SPREAD | Prefer nodes and zones holding no members of the group. Costs utilisation but means one node or zone failure cannot take out a whole fleet. |
| PLACEMENT_POLICY_STRICT_SPREAD_NODE | Hard requirement that no two members share a node. |
| PLACEMENT_POLICY_STRICT_SPREAD_ZONE | Hard requirement that no two members share a zone. |
What to do when the host must reclaim the instance.
| Value | Description |
|---|---|
| ON_HOST_MAINTENANCE_UNSPECIFIED | |
| ON_HOST_MAINTENANCE_MIGRATE | Live-migrate to another node. Preserves the session at the cost of a brief stall during the final memory copy. |
| ON_HOST_MAINTENANCE_TERMINATE | Terminate. Correct for stateless workloads that are cheaper to restart than to move. |
What to do when the workload stops unexpectedly.
| Value | Description |
|---|---|
| RESTART_POLICY_UNSPECIFIED | |
| RESTART_POLICY_NEVER | Never restart automatically. |
| RESTART_POLICY_ALWAYS | Restart on any exit, clean or not. |
| RESTART_POLICY_ON_FAILURE | Restart only after a non-zero exit or a failure of the machine it runs on. A clean shutdown is treated as intentional. |