PlanyDocs
API reference

SandboxService

api.plany.cloud:443Regional15 methods

Deploy a container image and get a URL that keeps working whether or not anything is running behind it. A sandbox scales from zero on requests, and can also be started by game events — a player joining, a server going quiet — through triggers, so you write the code that runs when something happens and nothing else. Creating a sandbox returns at once with a state to watch; every change to its template is a new revision, and a failed deploy leaves the previous one serving.

CreateSandbox

plany.sandbox.v1.SandboxService/CreateSandbox

Creates a sandbox and mints its first revision.

Returns immediately with STATE_PROVISIONING. The URL is assigned here and never changes, so it can be written into a client before anything has finished starting — which is the ordinary case for a terraform apply that wires a sandbox to something else in the same plan.

Parameters

  • parentstringRequired

    projects/{project}/locations/{location}.

  • sandboxSandboxRequired
    Show child attributes
    • templateSandboxTemplateRequired

      What to deploy.

      Writing this mints a revision. Reading it gives back what was last written, which is not necessarily what is serving: a revision that failed to bake leaves serving_revision on the one before it, and the difference between the two fields is exactly "a deploy that has not landed".

      Show child attributes
      • imagestringRequired

        Either a repository in Plany's container registry — projects/{project}/locations/{location}/repositories/{repository} followed by :{tag} or @{digest} — or an image in an external registry, written as docker pull would take it: ghcr.io/acme/api:1.4, docker.io/library/nginx@sha256:….

        A Plany repository must be in this project and region — the registry is regional and a pull crossing a region would be bytes leaving the failure domain the sandbox is in. An external image is pulled anonymously, or with image_pull_secret when the registry needs a credential.

        A tag is resolved to a digest when the revision is minted, and the digest is what is stored and pulled. So a tag that moves does not change a running revision, which is the property that makes serving_revision a rollback rather than a hope.

      • resourcesResourceRequirementsRequired

        CPU, memory and disk, in the same terms as a game server's.

        Note CpuSpec.reserved_millis against vcpus: the gap between them is burst headroom, and it is what makes a sandbox that is idle most of the time cheap without making it slow when it is not.

      • commandrepeated string

        Override the image's ENTRYPOINT. Empty uses the image's own.

        An argument list, never a shell string: no shell is run for you.

      • argsrepeated string

        Override the image's CMD. Empty uses the image's own.

      • envmap<string, string>

        Environment variables, merged over the image's own.

        For values that are not sensitive. A credential goes in secrets, where it is fetched by the container's own identity at launch and never sits in this message, in this service's database, in an audit entry, or on a console page.

      • Credentials to make available, fetched when an instance starts by the sandbox's own identity.

        Attaching allows this sandbox to read that one secret and stops the secret being deleted while it is attached. The value is never stored in the sandbox's configuration.

      • portint32

        What the container listens on. Defaults to 8080.

        One port. A sandbox is reached through one URL, and a second listener would be a second address to name, route, certificate and bill for — which is a product decision rather than a field.

      • protocolProtocol

        How the container is spoken to.

        The ingress terminates TLS either way and speaks this to the container, so a gRPC sandbox gets prior-knowledge h2c and an HTTP one does not — a distinction that cannot be sniffed reliably and must not be guessed.

        PROTOCOL_HTTP1PROTOCOL_H2C
      • max_concurrencyint32

        How many requests one instance handles at once. Defaults to 80.

        The number the autoscaler divides by, and the one customers get wrong in both directions. Set it to 1 for a container that is not concurrency-safe; leave it high for anything that spends its time waiting on something else.

      • request_timeoutDuration

        How long a request may take before the ingress gives up. Defaults to 5 minutes, capped at 60.

        Measured from the moment the request is accepted, which includes any wait for a scaled-to-zero instance to start. That is deliberate: it is the number the caller experiences, and a timeout that excluded the cold start would be a promise about the half the platform controls.

      • startup_timeoutDuration

        How long to wait for the container to accept its first connection before calling the start failed. Defaults to 4 minutes.

      • vpcstring

        A private network the container can reach, by its addresses, as well as the internet. Empty means none.

        A sandbox is not on a customer's network: its instances run on a network Plany manages for Sandbox in the project, which nothing else is on. This connects them to one more. Each instance gets an address in the network with no default route through it — only traffic for the network's own range goes that way — and nothing on the network can open a connection to it, whatever that network's firewall rules say. So a sandbox can call a database or a game server's admin port, and cannot be called back.

        On the template, so it is part of a revision: a deploy that connects a network can be rolled back to one that did not. Requires vpc.vpcs.use on the network, which must be in the sandbox's own project and region.

      • image_pull_secretstring

        The credential an external registry needs, as a Secrets Manager secret: projects/{project}/locations/{location}/secrets/{secret}, optionally with /versions/{version}. Empty pulls anonymously. Refused for an image in Plany's own registry, which is pulled by the sandbox's identity.

        The secret's payload is JSON, {"username": "…", "password": "…"} — the pair docker login takes, where the password is usually an access token.

        Used only to pull the image when a revision is deployed. Sandbox grants the sandbox's identity roles/secretmanager.accessor on this one secret; it is never put in the container's environment.

    • namestringIdentifier

      projects/{project}/locations/{location}/sandboxes/{sandbox}.

      Regional. The image is in one region's object store, the instances are on one region's hosts, and the URL names the region — a customer wanting two regions has a sandbox in each, which is the honest arrangement rather than a global name whose availability quietly depends on a region they never chose.

    • display_namestring

      What a person calls it. Free text.

    • descriptionstring
    • labelsmap<string, string>
    • Show child attributes
      • min_instancesint32

        Instances to keep running even with no traffic. Defaults to 0.

        Zero is scale-to-zero. One or more is a container that runs permanently, and is charged for continuously.

      • max_instancesint32

        Ceiling. Defaults to 10.

        A ceiling rather than a target, and the thing standing between a runaway trigger and a bill. It is also what sandbox.plany.cloud/max_instances is counted in, so it cannot be raised past what the project's quota allows.

      • idle_timeoutDuration

        How long an instance may sit with nothing in flight before it is released. Defaults to 15 minutes.

        Measured from the last request finishing, as counted by Plany in front of the container — the container is never asked whether it is busy.

    • ingressIngress
      INGRESS_PUBLICINGRESS_INTERNAL
    • serving_revisionstring

      The revision serving traffic, or empty to serve the newest READY one.

      Setting it is a rollback, and it is a rollback rather than a redeploy on purpose: the bytes are already baked, so it takes effect in the time it takes to start an instance rather than the time it takes to pull an image that may no longer exist under the tag it was pushed with.

    • service_accountstring

      accounts/{account}/serviceAccounts/{service_account}.

      The identity the container runs as, and what it pulls its image and reads its secrets with. Left empty, Plany provisions one, binds it narrowly, and revokes it on delete — the derived-name arrangement Playpute uses, which is what lets a teardown tell an identity the platform made from one a customer attached.

      Set it to attach an identity of your own, which requires iam.serviceAccounts.actAs on it. Plany grants a customer-supplied identity nothing: whatever it can reach, it could already reach.

    • annotationsmap<string, string>
  • sandbox_idstringRequired

    The last segment of the name. AIP-122: lower case, digits and hyphens.

    It appears in the URL, so it is also a DNS label: at most 40 characters, and not ending in a hyphen.

  • request_idstring

    An idempotency key. A retry with the same value returns the sandbox the first call created rather than colliding.

Returns

Sandbox.

grpcurl -H "authorization: Bearer $PLANY_API_KEY" \
-d '{
"parent": "projects//locations/",
"sandbox": {
"display_name": "My server",
"template": {
"image": "",
"resources": {
"cpu": {},
"memory": {}
}
}
},
"sandbox_id": "my-sandbox"
}' \
api.plany.cloud:443 plany.sandbox.v1.SandboxService/CreateSandbox
Example response
{
"name": "projects//locations//sandboxes/",
"uid": "0b6f3c1e-8a2d-4f6e-9c1a-2f4b7d9e0a11",
"display_name": "My server",
"description": "",
"labels": {
"key": ""
},
"template": {
"image": "",
"command": [
""
],
"args": [
""
],
"env": {
"key": ""
},
"secrets": [
{
"secret": "projects//locations//secrets/",
"environment_variable": "",
"file_path": ""
}
],
"port": 0,
"protocol": "PROTOCOL_HTTP1",
"resources": {},
"max_concurrency": 0,
"request_timeout": "300s",
"startup_timeout": "300s",
"vpc": "projects//locations//vpcs/",
"image_pull_secret": "projects//locations//secrets/"
},
"scaling": {
"min_instances": 0,
"max_instances": 0,
"idle_timeout": "300s"
},
"ingress": "INGRESS_PUBLIC",
"serving_revision": "projects//locations//sandboxes//revisions/my-revision",
"latest_revision": "projects//locations//sandboxes//revisions/my-revision",
"url": "",
"service_account": "accounts//serviceAccounts/my-service-account",
"state": "STATE_PROVISIONING",
"state_reason": "",
"instance_count": 0,
"has_instance_count": false,
"annotations": {
"key": ""
},
"generation": "0",
"observed_generation": "0",
"create_time": "2026-10-04T12:00:00Z",
"update_time": "2026-10-04T12:00:00Z",
"etag": "W/\"1\""
}

GetSandbox

Read
plany.sandbox.v1.SandboxService/GetSandbox

Parameters

  • namestringRequired

Returns

Sandbox.

grpcurl -H "authorization: Bearer $PLANY_API_KEY" \
-d '{
"name": "projects//locations//sandboxes/"
}' \
api.plany.cloud:443 plany.sandbox.v1.SandboxService/GetSandbox
Example response
{
"name": "projects//locations//sandboxes/",
"uid": "0b6f3c1e-8a2d-4f6e-9c1a-2f4b7d9e0a11",
"display_name": "My server",
"description": "",
"labels": {
"key": ""
},
"template": {
"image": "",
"command": [
""
],
"args": [
""
],
"env": {
"key": ""
},
"secrets": [
{
"secret": "projects//locations//secrets/",
"environment_variable": "",
"file_path": ""
}
],
"port": 0,
"protocol": "PROTOCOL_HTTP1",
"resources": {},
"max_concurrency": 0,
"request_timeout": "300s",
"startup_timeout": "300s",
"vpc": "projects//locations//vpcs/",
"image_pull_secret": "projects//locations//secrets/"
},
"scaling": {
"min_instances": 0,
"max_instances": 0,
"idle_timeout": "300s"
},
"ingress": "INGRESS_PUBLIC",
"serving_revision": "projects//locations//sandboxes//revisions/my-revision",
"latest_revision": "projects//locations//sandboxes//revisions/my-revision",
"url": "",
"service_account": "accounts//serviceAccounts/my-service-account",
"state": "STATE_PROVISIONING",
"state_reason": "",
"instance_count": 0,
"has_instance_count": false,
"annotations": {
"key": ""
},
"generation": "0",
"observed_generation": "0",
"create_time": "2026-10-04T12:00:00Z",
"update_time": "2026-10-04T12:00:00Z",
"etag": "W/\"1\""
}

ListSandboxes

Read
plany.sandbox.v1.SandboxService/ListSandboxes

Parameters

  • parentstringRequired
  • page_sizeint32
  • page_tokenstring
  • filterstring

    AIP-160, over display_name, labels and state.

Returns

ListSandboxesResponse.

grpcurl -H "authorization: Bearer $PLANY_API_KEY" \
-d '{
"parent": "projects//locations/",
"page_size": 50
}' \
api.plany.cloud:443 plany.sandbox.v1.SandboxService/ListSandboxes
Example response
{
"sandboxes": [
{
"name": "projects//locations//sandboxes/",
"uid": "0b6f3c1e-8a2d-4f6e-9c1a-2f4b7d9e0a11",
"display_name": "My server",
"description": "",
"labels": {
"key": ""
},
"template": {
"image": "",
"command": [
""
],
"args": [
""
],
"env": {
"key": ""
},
"port": 0,
"protocol": "PROTOCOL_HTTP1",
"max_concurrency": 0,
"request_timeout": "300s",
"startup_timeout": "300s",
"vpc": "projects//locations//vpcs/",
"image_pull_secret": "projects//locations//secrets/"
},
"scaling": {
"min_instances": 0,
"max_instances": 0,
"idle_timeout": "300s"
},
"ingress": "INGRESS_PUBLIC",
"serving_revision": "projects//locations//sandboxes//revisions/my-revision",
"latest_revision": "projects//locations//sandboxes//revisions/my-revision",
"url": "",
"service_account": "accounts//serviceAccounts/my-service-account",
"state": "STATE_PROVISIONING",
"state_reason": "",
"instance_count": 0,
"has_instance_count": false,
"annotations": {
"key": ""
},
"generation": "0",
"observed_generation": "0",
"create_time": "2026-10-04T12:00:00Z",
"update_time": "2026-10-04T12:00:00Z",
"etag": "W/\"1\""
}
],
"next_page_token": ""
}

UpdateSandbox

plany.sandbox.v1.SandboxService/UpdateSandbox

Updates a sandbox. Changing template deploys a new revision.

template is replaced as a whole when it is in the update mask, not merged: send only a new image and the environment, secrets and resources are dropped. Read the current template, change what you need, and send all of it.

Parameters

  • sandboxSandboxRequired
    Show child attributes
    • templateSandboxTemplateRequired

      What to deploy.

      Writing this mints a revision. Reading it gives back what was last written, which is not necessarily what is serving: a revision that failed to bake leaves serving_revision on the one before it, and the difference between the two fields is exactly "a deploy that has not landed".

      Show child attributes
      • imagestringRequired

        Either a repository in Plany's container registry — projects/{project}/locations/{location}/repositories/{repository} followed by :{tag} or @{digest} — or an image in an external registry, written as docker pull would take it: ghcr.io/acme/api:1.4, docker.io/library/nginx@sha256:….

        A Plany repository must be in this project and region — the registry is regional and a pull crossing a region would be bytes leaving the failure domain the sandbox is in. An external image is pulled anonymously, or with image_pull_secret when the registry needs a credential.

        A tag is resolved to a digest when the revision is minted, and the digest is what is stored and pulled. So a tag that moves does not change a running revision, which is the property that makes serving_revision a rollback rather than a hope.

      • resourcesResourceRequirementsRequired

        CPU, memory and disk, in the same terms as a game server's.

        Note CpuSpec.reserved_millis against vcpus: the gap between them is burst headroom, and it is what makes a sandbox that is idle most of the time cheap without making it slow when it is not.

      • commandrepeated string

        Override the image's ENTRYPOINT. Empty uses the image's own.

        An argument list, never a shell string: no shell is run for you.

      • argsrepeated string

        Override the image's CMD. Empty uses the image's own.

      • envmap<string, string>

        Environment variables, merged over the image's own.

        For values that are not sensitive. A credential goes in secrets, where it is fetched by the container's own identity at launch and never sits in this message, in this service's database, in an audit entry, or on a console page.

      • Credentials to make available, fetched when an instance starts by the sandbox's own identity.

        Attaching allows this sandbox to read that one secret and stops the secret being deleted while it is attached. The value is never stored in the sandbox's configuration.

      • portint32

        What the container listens on. Defaults to 8080.

        One port. A sandbox is reached through one URL, and a second listener would be a second address to name, route, certificate and bill for — which is a product decision rather than a field.

      • protocolProtocol

        How the container is spoken to.

        The ingress terminates TLS either way and speaks this to the container, so a gRPC sandbox gets prior-knowledge h2c and an HTTP one does not — a distinction that cannot be sniffed reliably and must not be guessed.

        PROTOCOL_HTTP1PROTOCOL_H2C
      • max_concurrencyint32

        How many requests one instance handles at once. Defaults to 80.

        The number the autoscaler divides by, and the one customers get wrong in both directions. Set it to 1 for a container that is not concurrency-safe; leave it high for anything that spends its time waiting on something else.

      • request_timeoutDuration

        How long a request may take before the ingress gives up. Defaults to 5 minutes, capped at 60.

        Measured from the moment the request is accepted, which includes any wait for a scaled-to-zero instance to start. That is deliberate: it is the number the caller experiences, and a timeout that excluded the cold start would be a promise about the half the platform controls.

      • startup_timeoutDuration

        How long to wait for the container to accept its first connection before calling the start failed. Defaults to 4 minutes.

      • vpcstring

        A private network the container can reach, by its addresses, as well as the internet. Empty means none.

        A sandbox is not on a customer's network: its instances run on a network Plany manages for Sandbox in the project, which nothing else is on. This connects them to one more. Each instance gets an address in the network with no default route through it — only traffic for the network's own range goes that way — and nothing on the network can open a connection to it, whatever that network's firewall rules say. So a sandbox can call a database or a game server's admin port, and cannot be called back.

        On the template, so it is part of a revision: a deploy that connects a network can be rolled back to one that did not. Requires vpc.vpcs.use on the network, which must be in the sandbox's own project and region.

      • image_pull_secretstring

        The credential an external registry needs, as a Secrets Manager secret: projects/{project}/locations/{location}/secrets/{secret}, optionally with /versions/{version}. Empty pulls anonymously. Refused for an image in Plany's own registry, which is pulled by the sandbox's identity.

        The secret's payload is JSON, {"username": "…", "password": "…"} — the pair docker login takes, where the password is usually an access token.

        Used only to pull the image when a revision is deployed. Sandbox grants the sandbox's identity roles/secretmanager.accessor on this one secret; it is never put in the container's environment.

    • namestringIdentifier

      projects/{project}/locations/{location}/sandboxes/{sandbox}.

      Regional. The image is in one region's object store, the instances are on one region's hosts, and the URL names the region — a customer wanting two regions has a sandbox in each, which is the honest arrangement rather than a global name whose availability quietly depends on a region they never chose.

    • display_namestring

      What a person calls it. Free text.

    • descriptionstring
    • labelsmap<string, string>
    • Show child attributes
      • min_instancesint32

        Instances to keep running even with no traffic. Defaults to 0.

        Zero is scale-to-zero. One or more is a container that runs permanently, and is charged for continuously.

      • max_instancesint32

        Ceiling. Defaults to 10.

        A ceiling rather than a target, and the thing standing between a runaway trigger and a bill. It is also what sandbox.plany.cloud/max_instances is counted in, so it cannot be raised past what the project's quota allows.

      • idle_timeoutDuration

        How long an instance may sit with nothing in flight before it is released. Defaults to 15 minutes.

        Measured from the last request finishing, as counted by Plany in front of the container — the container is never asked whether it is busy.

    • ingressIngress
      INGRESS_PUBLICINGRESS_INTERNAL
    • serving_revisionstring

      The revision serving traffic, or empty to serve the newest READY one.

      Setting it is a rollback, and it is a rollback rather than a redeploy on purpose: the bytes are already baked, so it takes effect in the time it takes to start an instance rather than the time it takes to pull an image that may no longer exist under the tag it was pushed with.

    • service_accountstring

      accounts/{account}/serviceAccounts/{service_account}.

      The identity the container runs as, and what it pulls its image and reads its secrets with. Left empty, Plany provisions one, binds it narrowly, and revokes it on delete — the derived-name arrangement Playpute uses, which is what lets a teardown tell an identity the platform made from one a customer attached.

      Set it to attach an identity of your own, which requires iam.serviceAccounts.actAs on it. Plany grants a customer-supplied identity nothing: whatever it can reach, it could already reach.

    • annotationsmap<string, string>
  • update_maskFieldMaskRequired

    Which fields to write.

    display_name, description, labels, annotations, template, scaling, ingress and serving_revision. Naming template mints a revision and replaces the whole message — see the method comment.

Returns

Sandbox.

grpcurl -H "authorization: Bearer $PLANY_API_KEY" \
-d '{
"sandbox": {
"display_name": "My server",
"template": {
"image": "",
"resources": {
"cpu": {},
"memory": {}
}
}
},
"update_mask": "display_name"
}' \
api.plany.cloud:443 plany.sandbox.v1.SandboxService/UpdateSandbox
Example response
{
"name": "projects//locations//sandboxes/",
"uid": "0b6f3c1e-8a2d-4f6e-9c1a-2f4b7d9e0a11",
"display_name": "My server",
"description": "",
"labels": {
"key": ""
},
"template": {
"image": "",
"command": [
""
],
"args": [
""
],
"env": {
"key": ""
},
"secrets": [
{
"secret": "projects//locations//secrets/",
"environment_variable": "",
"file_path": ""
}
],
"port": 0,
"protocol": "PROTOCOL_HTTP1",
"resources": {},
"max_concurrency": 0,
"request_timeout": "300s",
"startup_timeout": "300s",
"vpc": "projects//locations//vpcs/",
"image_pull_secret": "projects//locations//secrets/"
},
"scaling": {
"min_instances": 0,
"max_instances": 0,
"idle_timeout": "300s"
},
"ingress": "INGRESS_PUBLIC",
"serving_revision": "projects//locations//sandboxes//revisions/my-revision",
"latest_revision": "projects//locations//sandboxes//revisions/my-revision",
"url": "",
"service_account": "accounts//serviceAccounts/my-service-account",
"state": "STATE_PROVISIONING",
"state_reason": "",
"instance_count": 0,
"has_instance_count": false,
"annotations": {
"key": ""
},
"generation": "0",
"observed_generation": "0",
"create_time": "2026-10-04T12:00:00Z",
"update_time": "2026-10-04T12:00:00Z",
"etag": "W/\"1\""
}

DeleteSandbox

plany.sandbox.v1.SandboxService/DeleteSandbox

Deletes a sandbox, its revisions, its triggers and its instances.

The URL stops answering immediately. A service account you attached is left alone.

Parameters

  • namestringRequired
  • etagstring

    Refuse if the sandbox has changed since this etag was read.

Returns

Empty.

grpcurl -H "authorization: Bearer $PLANY_API_KEY" \
-d '{
"name": "projects//locations//sandboxes/"
}' \
api.plany.cloud:443 plany.sandbox.v1.SandboxService/DeleteSandbox
Example response
{}

GetSandboxRevision

Read
plany.sandbox.v1.SandboxService/GetSandboxRevision

Parameters

  • namestringRequired

Returns

SandboxRevision.

grpcurl -H "authorization: Bearer $PLANY_API_KEY" \
-d '{
"name": "projects//locations//sandboxes//revisions/my-revision"
}' \
api.plany.cloud:443 plany.sandbox.v1.SandboxService/GetSandboxRevision
Example response
{
"name": "projects//locations//sandboxes//revisions/my-revision",
"uid": "0b6f3c1e-8a2d-4f6e-9c1a-2f4b7d9e0a11",
"template": {
"image": "",
"command": [
""
],
"args": [
""
],
"env": {
"key": ""
},
"secrets": [
{
"secret": "projects//locations//secrets/",
"environment_variable": "",
"file_path": ""
}
],
"port": 0,
"protocol": "PROTOCOL_HTTP1",
"resources": {},
"max_concurrency": 0,
"request_timeout": "300s",
"startup_timeout": "300s",
"vpc": "projects//locations//vpcs/",
"image_pull_secret": "projects//locations//secrets/"
},
"image_digest": "",
"schematic": "",
"state": "STATE_PROVISIONING",
"state_reason": "",
"created_by": "",
"create_time": "2026-10-04T12:00:00Z",
"ready_time": "2026-10-04T12:00:00Z"
}

ListSandboxRevisions

Read
plany.sandbox.v1.SandboxService/ListSandboxRevisions

Lists revisions, newest first. What a rollback picks from.

Parameters

  • parentstringRequired
  • page_sizeint32
  • page_tokenstring

Returns

ListSandboxRevisionsResponse.

grpcurl -H "authorization: Bearer $PLANY_API_KEY" \
-d '{
"parent": "projects//locations//sandboxes/",
"page_size": 50
}' \
api.plany.cloud:443 plany.sandbox.v1.SandboxService/ListSandboxRevisions
Example response
{
"revisions": [
{
"name": "projects//locations//sandboxes//revisions/my-revision",
"uid": "0b6f3c1e-8a2d-4f6e-9c1a-2f4b7d9e0a11",
"template": {
"image": "",
"command": [
""
],
"args": [
""
],
"env": {
"key": ""
},
"port": 0,
"protocol": "PROTOCOL_HTTP1",
"max_concurrency": 0,
"request_timeout": "300s",
"startup_timeout": "300s",
"vpc": "projects//locations//vpcs/",
"image_pull_secret": "projects//locations//secrets/"
},
"image_digest": "",
"schematic": "",
"state": "STATE_PROVISIONING",
"state_reason": "",
"created_by": "",
"create_time": "2026-10-04T12:00:00Z",
"ready_time": "2026-10-04T12:00:00Z"
}
],
"next_page_token": ""
}

DeleteSandboxRevision

plany.sandbox.v1.SandboxService/DeleteSandboxRevision

Deletes a revision and the disk baked for it.

Refused for the revision currently serving, and for the newest READY one — deleting the thing a rollback would land on is a decision made once and regretted under pressure.

Parameters

  • namestringRequired

Returns

Empty.

grpcurl -H "authorization: Bearer $PLANY_API_KEY" \
-d '{
"name": "projects//locations//sandboxes//revisions/my-revision"
}' \
api.plany.cloud:443 plany.sandbox.v1.SandboxService/DeleteSandboxRevision
Example response
{}

CreateSandboxTrigger

plany.sandbox.v1.SandboxService/CreateSandboxTrigger

Parameters

  • parentstringRequired

    projects/{project}/locations/{location}/sandboxes/{sandbox}.

  • triggerSandboxTriggerRequired
    Show child attributes
    • event_typeEventTypeRequired

      What to deliver.

      EVENT_TYPE_CONNECTION_OPENEDEVENT_TYPE_CONNECTION_CLOSEDEVENT_TYPE_PLAYER_JOINEDEVENT_TYPE_PLAYER_LEFTEVENT_TYPE_SERVER_READYEVENT_TYPE_SERVER_STOPPING
    • namestringIdentifier
    • display_namestring
    • game_serverstring

      Deliver only events about this game server.

      projects/{project}/locations/{location}/gameServers/{game_server}, or empty for every game server in the project. The narrow case is the common one — a trigger for a specific fleet — and the wide one is what a project's analytics wants.

    • pathstring

      The request path events are delivered to. Defaults to /.

      So one sandbox can serve several triggers and route on the path, which is how an ordinary web framework already wants to be written.

    • STATE_ACTIVESTATE_PAUSED
  • trigger_idstringRequired

Returns

SandboxTrigger.

grpcurl -H "authorization: Bearer $PLANY_API_KEY" \
-d '{
"parent": "projects//locations//sandboxes/",
"trigger": {
"display_name": "My server",
"event_type": "EVENT_TYPE_CONNECTION_OPENED"
},
"trigger_id": "my-trigger"
}' \
api.plany.cloud:443 plany.sandbox.v1.SandboxService/CreateSandboxTrigger
Example response
{
"name": "projects//locations//sandboxes//triggers/my-trigger",
"uid": "0b6f3c1e-8a2d-4f6e-9c1a-2f4b7d9e0a11",
"display_name": "My server",
"event_type": "EVENT_TYPE_CONNECTION_OPENED",
"game_server": "",
"path": "",
"state": "STATE_ACTIVE",
"failed_delivery_count": "0",
"last_delivery_time": "2026-10-04T12:00:00Z",
"create_time": "2026-10-04T12:00:00Z",
"update_time": "2026-10-04T12:00:00Z",
"etag": "W/\"1\""
}

GetSandboxTrigger

Read
plany.sandbox.v1.SandboxService/GetSandboxTrigger

Parameters

  • namestringRequired

Returns

SandboxTrigger.

grpcurl -H "authorization: Bearer $PLANY_API_KEY" \
-d '{
"name": "projects//locations//sandboxes//triggers/my-trigger"
}' \
api.plany.cloud:443 plany.sandbox.v1.SandboxService/GetSandboxTrigger
Example response
{
"name": "projects//locations//sandboxes//triggers/my-trigger",
"uid": "0b6f3c1e-8a2d-4f6e-9c1a-2f4b7d9e0a11",
"display_name": "My server",
"event_type": "EVENT_TYPE_CONNECTION_OPENED",
"game_server": "",
"path": "",
"state": "STATE_ACTIVE",
"failed_delivery_count": "0",
"last_delivery_time": "2026-10-04T12:00:00Z",
"create_time": "2026-10-04T12:00:00Z",
"update_time": "2026-10-04T12:00:00Z",
"etag": "W/\"1\""
}

ListSandboxTriggers

Read
plany.sandbox.v1.SandboxService/ListSandboxTriggers

Parameters

  • parentstringRequired
  • page_sizeint32
  • page_tokenstring

Returns

ListSandboxTriggersResponse.

grpcurl -H "authorization: Bearer $PLANY_API_KEY" \
-d '{
"parent": "projects//locations//sandboxes/",
"page_size": 50
}' \
api.plany.cloud:443 plany.sandbox.v1.SandboxService/ListSandboxTriggers
Example response
{
"triggers": [
{
"name": "projects//locations//sandboxes//triggers/my-trigger",
"uid": "0b6f3c1e-8a2d-4f6e-9c1a-2f4b7d9e0a11",
"display_name": "My server",
"event_type": "EVENT_TYPE_CONNECTION_OPENED",
"game_server": "",
"path": "",
"state": "STATE_ACTIVE",
"failed_delivery_count": "0",
"last_delivery_time": "2026-10-04T12:00:00Z",
"create_time": "2026-10-04T12:00:00Z",
"update_time": "2026-10-04T12:00:00Z",
"etag": "W/\"1\""
}
],
"next_page_token": ""
}

UpdateSandboxTrigger

plany.sandbox.v1.SandboxService/UpdateSandboxTrigger

Parameters

  • triggerSandboxTriggerRequired
    Show child attributes
    • event_typeEventTypeRequired

      What to deliver.

      EVENT_TYPE_CONNECTION_OPENEDEVENT_TYPE_CONNECTION_CLOSEDEVENT_TYPE_PLAYER_JOINEDEVENT_TYPE_PLAYER_LEFTEVENT_TYPE_SERVER_READYEVENT_TYPE_SERVER_STOPPING
    • namestringIdentifier
    • display_namestring
    • game_serverstring

      Deliver only events about this game server.

      projects/{project}/locations/{location}/gameServers/{game_server}, or empty for every game server in the project. The narrow case is the common one — a trigger for a specific fleet — and the wide one is what a project's analytics wants.

    • pathstring

      The request path events are delivered to. Defaults to /.

      So one sandbox can serve several triggers and route on the path, which is how an ordinary web framework already wants to be written.

    • STATE_ACTIVESTATE_PAUSED
  • update_maskFieldMaskRequired

    display_name, event_type, game_server, path and state.

Returns

SandboxTrigger.

grpcurl -H "authorization: Bearer $PLANY_API_KEY" \
-d '{
"trigger": {
"display_name": "My server",
"event_type": "EVENT_TYPE_CONNECTION_OPENED"
},
"update_mask": "display_name"
}' \
api.plany.cloud:443 plany.sandbox.v1.SandboxService/UpdateSandboxTrigger
Example response
{
"name": "projects//locations//sandboxes//triggers/my-trigger",
"uid": "0b6f3c1e-8a2d-4f6e-9c1a-2f4b7d9e0a11",
"display_name": "My server",
"event_type": "EVENT_TYPE_CONNECTION_OPENED",
"game_server": "",
"path": "",
"state": "STATE_ACTIVE",
"failed_delivery_count": "0",
"last_delivery_time": "2026-10-04T12:00:00Z",
"create_time": "2026-10-04T12:00:00Z",
"update_time": "2026-10-04T12:00:00Z",
"etag": "W/\"1\""
}

DeleteSandboxTrigger

plany.sandbox.v1.SandboxService/DeleteSandboxTrigger

Parameters

  • namestringRequired

Returns

Empty.

grpcurl -H "authorization: Bearer $PLANY_API_KEY" \
-d '{
"name": "projects//locations//sandboxes//triggers/my-trigger"
}' \
api.plany.cloud:443 plany.sandbox.v1.SandboxService/DeleteSandboxTrigger
Example response
{}

ListFailedDeliveries

Read
plany.sandbox.v1.SandboxService/ListFailedDeliveries

Lists deliveries that were abandoned after exhausting their retries.

The one place a customer finds out that their code did not run. A trigger whose sandbox has been failing for an hour is otherwise indistinguishable from a quiet evening, and a dropped player-join is business logic that did not happen.

Parameters

  • parentstringRequired

    projects/{project}/locations/{location}/sandboxes/{sandbox}.

  • page_sizeint32
  • page_tokenstring
  • triggerstring

    Only deliveries for this trigger.

Returns

ListFailedDeliveriesResponse.

grpcurl -H "authorization: Bearer $PLANY_API_KEY" \
-d '{
"parent": "",
"page_size": 50
}' \
api.plany.cloud:443 plany.sandbox.v1.SandboxService/ListFailedDeliveries
Example response
{
"deliveries": [
{
"trigger": "",
"event_type": "EVENT_TYPE_CONNECTION_OPENED",
"event_id": "my-event",
"game_server": "",
"reason": "",
"attempts": 0,
"event_time": "2026-10-04T12:00:00Z",
"last_attempt_time": "2026-10-04T12:00:00Z"
}
],
"next_page_token": ""
}

ReportBakeResult

plany.sandbox.v1.SandboxService/ReportBakeResult

Reports how a revision's deploy ended, from inside the machine installing its image.

Called by Plany's own agent on that machine — you do not call it. It is how a revision deployed from a tag in another registry comes to show the digest the tag resolved to, and how an image that can never be installed — a refused credential, a tag that does not exist, an image with no linux/amd64 variant — fails the deploy at once with that reason in state_reason.

Parameters

  • namestringRequired

    The revision being baked: projects/{project}/locations/{location}/sandboxes/{sandbox}/revisions/{revision}.

    name rather than revision, as on every other method that acts on one existing resource — the field every reflection-based gate on the platform looks for to find whose resource a call is about.

  • bake_tokenstringRequired

    A one-time value given only to the machine installing this revision, and refused once the deploy has finished.

  • image_digeststringOne of outcome

    The digest the image's tag resolved to, sha256: and 64 lower-case hex characters. Recorded only if the revision has no digest yet; a different digest from the one recorded is refused, because it means the tag moved during the deploy.

  • failure_reasonstringOne of outcome

    Why the image cannot be installed, in one sentence. Fails the revision.

Returns

Empty.

grpcurl -H "authorization: Bearer $PLANY_API_KEY" \
-d '{
"name": "projects//locations//sandboxes//revisions/my-revision",
"bake_token": ""
}' \
api.plany.cloud:443 plany.sandbox.v1.SandboxService/ReportBakeResult
Example response
{}

The FailedDelivery object

FailedDelivery is one event a sandbox never successfully received.

Attributes

  • triggerstring
  • event_typeEventType
    EVENT_TYPE_CONNECTION_OPENEDEVENT_TYPE_CONNECTION_CLOSEDEVENT_TYPE_PLAYER_JOINEDEVENT_TYPE_PLAYER_LEFTEVENT_TYPE_SERVER_READYEVENT_TYPE_SERVER_STOPPING
  • event_idstring

    The event's id, to quote when asking Plany support about it.

  • game_serverstring

    What the game server was, when the event named one.

  • reasonstring

    The last failure, in words. An HTTP status, a timeout, a sandbox that could not be started.

  • attemptsint32
  • event_timeTimestamp
  • last_attempt_timeTimestamp
FailedDelivery
{
"trigger": "",
"event_type": "EVENT_TYPE_CONNECTION_OPENED",
"event_id": "my-event",
"game_server": "",
"reason": "",
"attempts": 0,
"event_time": "2026-10-04T12:00:00Z",
"last_attempt_time": "2026-10-04T12:00:00Z"
}

The SandboxTemplate object

SandboxTemplate is what a revision is made of.

Every field here is part of the revision's identity: changing any of them mints a new revision, because a container started with a different environment is a different deployment and pretending otherwise makes a rollback restore something the customer did not have.

Attributes

  • imagestringRequired

    Either a repository in Plany's container registry — projects/{project}/locations/{location}/repositories/{repository} followed by :{tag} or @{digest} — or an image in an external registry, written as docker pull would take it: ghcr.io/acme/api:1.4, docker.io/library/nginx@sha256:….

    A Plany repository must be in this project and region — the registry is regional and a pull crossing a region would be bytes leaving the failure domain the sandbox is in. An external image is pulled anonymously, or with image_pull_secret when the registry needs a credential.

    A tag is resolved to a digest when the revision is minted, and the digest is what is stored and pulled. So a tag that moves does not change a running revision, which is the property that makes serving_revision a rollback rather than a hope.

  • resourcesResourceRequirementsRequired

    CPU, memory and disk, in the same terms as a game server's.

    Note CpuSpec.reserved_millis against vcpus: the gap between them is burst headroom, and it is what makes a sandbox that is idle most of the time cheap without making it slow when it is not.

    Show child attributes
    • cpuCpuSpecRequired
      Show child attributes
      • vcpusint32Required

        Number of virtual CPUs the workload sees. Required, must be at least 1.

      • reserved_millisint64

        CPU 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_millisint64

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

      • architectureCpuArchitecture
        CPU_ARCHITECTURE_X86_64CPU_ARCHITECTURE_ARM64
      • required_featuresrepeated string

        CPUID 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_coresbool

        Pin 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_siblingsbool

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

    • memoryMemorySpecRequired
      Show child attributes
      • size_bytesint64Required

        Memory the workload sees, in bytes. Required.

      • reserved_bytesint64

        Memory 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_enabledbool

        Allow memory the workload is not using to be reclaimed. Required when reserved_bytes is less than size_bytes.

      • hugepagesbool

        Back 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_bytesint64

        Allow memory to grow to this size while running, without a restart. Zero disables it. Must be at least size_bytes when set.

      • sharedbool

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

    • Block devices, in attachment order. Exactly one must set boot unless the workload boots from an initramfs alone.

      Show child attributes
      • disk_idstringRequired

        Identifier for this disk within the workload, e.g. "root" or "world-data". Must be unique within it.

      • sourceDiskSpec.SourceRequired
      • size_bytesint64

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

      • DISK_TYPE_LOCAL_NVMEDISK_TYPE_NETWORK_SSDDISK_TYPE_NETWORK_SSD_HIGH_IOPS
      • read_onlybool

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

      • bootbool

        Marks the disk the workload boots from. Exactly one disk may set this.

      • delete_on_instance_deletebool

        Delete the underlying storage when the instance is deleted. Always true in effect for LOCAL_NVME.

      • direct_iobool

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

    • Show child attributes
      • interface_idstringRequired

        Stable identifier within the instance, e.g. "eth0".

      • networkstring

        Private network to attach to, as a resource name. Empty means the project's default network in this location.

      • private_ipv4string

        Requested private IPv4 address. Empty allocates one from the subnet.

      • PUBLIC_ADDRESS_MODE_NONEPUBLIC_ADDRESS_MODE_SHARED_IPV4PUBLIC_ADDRESS_MODE_DEDICATED_IPV4PUBLIC_ADDRESS_MODE_IPV6
      • NETWORK_TIER_STANDARDNETWORK_TIER_PREMIUM
      • mac_addressstring

        The interface's MAC address. Empty generates one. Pinning it matters for game licences tied to a MAC.

      • multi_queuebool

        Enable multi-queue virtio-net with one queue pair per vCPU. Raises packet throughput for servers handling many concurrent clients.

      • tagsrepeated string

        Network 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_inboundbool

        Refuse 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_routebool

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

    • placementPlacementSpec
      Show child attributes
      • zonesrepeated string

        Zones 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 NodeSelectorRequirement

        Hard node requirements. A node must satisfy every entry.

      • preferred_node_selectorsrepeated NodeSelectorRequirement

        Soft node preferences. Nodes satisfying more of these score higher, but none of them can make placement fail.

      • placement_groupstring

        Group this workload belongs to, scoped to the project. Workloads sharing a group are spread out or packed together according to policy.

      • PLACEMENT_POLICY_PACKPLACEMENT_POLICY_SPREADPLACEMENT_POLICY_STRICT_SPREAD_NODEPLACEMENT_POLICY_STRICT_SPREAD_ZONE
      • capacity_classstring

        Class of capacity to draw from. Leave empty to let Plany choose, which is right for almost every workload.

      • disallow_preemptiblebool

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

      • nodestring

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

    • schedulingSchedulingSpec
      Show child attributes
      • ON_HOST_MAINTENANCE_MIGRATEON_HOST_MAINTENANCE_TERMINATE
      • RESTART_POLICY_NEVERRESTART_POLICY_ALWAYSRESTART_POLICY_ON_FAILURE
      • preemptiblebool

        Run as spot: on spare capacity, at the spot rate, reclaimable with two minutes' notice when an on-demand workload needs the room.

      • preemption_noticeDuration

        Warning 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_durationDuration

        Stop 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_periodDuration

        Time between the shutdown signal and a forced stop, so a game server can save its world. Zero uses the default of thirty seconds.

      • preemptible_fallbackbool

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

  • commandrepeated string

    Override the image's ENTRYPOINT. Empty uses the image's own.

    An argument list, never a shell string: no shell is run for you.

  • argsrepeated string

    Override the image's CMD. Empty uses the image's own.

  • envmap<string, string>

    Environment variables, merged over the image's own.

    For values that are not sensitive. A credential goes in secrets, where it is fetched by the container's own identity at launch and never sits in this message, in this service's database, in an audit entry, or on a console page.

  • Credentials to make available, fetched when an instance starts by the sandbox's own identity.

    Attaching allows this sandbox to read that one secret and stops the secret being deleted while it is attached. The value is never stored in the sandbox's configuration.

    Show child attributes
    • secretstringRequired

      projects/{project}/locations/{location}/secrets/{secret}, optionally with /versions/{version}.

      Unqualified resolves to the latest enabled version, so a rotation reaches this container on its next start with nothing to redeploy. Pin a version only when a revision must not follow a rotation.

    • environment_variablestringRequired

      The environment variable the container sees it as.

      Upper case, digits and underscores, not starting with a digit. The payload must be valid UTF-8 to be injected this way; a binary key is refused at launch rather than silently mangled.

    • file_pathstring

      Also write the value to this absolute path inside the container.

      For the things that will not go in a variable: a service account JSON, a TLS key, a licence file something reads by path. Written 0600, owned by the container's user, and rewritten every launch — this is the platform's copy of a value the platform holds, so a rotation cannot leave a stale one behind.

  • portint32

    What the container listens on. Defaults to 8080.

    One port. A sandbox is reached through one URL, and a second listener would be a second address to name, route, certificate and bill for — which is a product decision rather than a field.

  • protocolProtocol

    How the container is spoken to.

    The ingress terminates TLS either way and speaks this to the container, so a gRPC sandbox gets prior-knowledge h2c and an HTTP one does not — a distinction that cannot be sniffed reliably and must not be guessed.

    PROTOCOL_HTTP1PROTOCOL_H2C
  • max_concurrencyint32

    How many requests one instance handles at once. Defaults to 80.

    The number the autoscaler divides by, and the one customers get wrong in both directions. Set it to 1 for a container that is not concurrency-safe; leave it high for anything that spends its time waiting on something else.

  • request_timeoutDuration

    How long a request may take before the ingress gives up. Defaults to 5 minutes, capped at 60.

    Measured from the moment the request is accepted, which includes any wait for a scaled-to-zero instance to start. That is deliberate: it is the number the caller experiences, and a timeout that excluded the cold start would be a promise about the half the platform controls.

  • startup_timeoutDuration

    How long to wait for the container to accept its first connection before calling the start failed. Defaults to 4 minutes.

  • vpcstring

    A private network the container can reach, by its addresses, as well as the internet. Empty means none.

    A sandbox is not on a customer's network: its instances run on a network Plany manages for Sandbox in the project, which nothing else is on. This connects them to one more. Each instance gets an address in the network with no default route through it — only traffic for the network's own range goes that way — and nothing on the network can open a connection to it, whatever that network's firewall rules say. So a sandbox can call a database or a game server's admin port, and cannot be called back.

    On the template, so it is part of a revision: a deploy that connects a network can be rolled back to one that did not. Requires vpc.vpcs.use on the network, which must be in the sandbox's own project and region.

  • image_pull_secretstring

    The credential an external registry needs, as a Secrets Manager secret: projects/{project}/locations/{location}/secrets/{secret}, optionally with /versions/{version}. Empty pulls anonymously. Refused for an image in Plany's own registry, which is pulled by the sandbox's identity.

    The secret's payload is JSON, {"username": "…", "password": "…"} — the pair docker login takes, where the password is usually an access token.

    Used only to pull the image when a revision is deployed. Sandbox grants the sandbox's identity roles/secretmanager.accessor on this one secret; it is never put in the container's environment.

SandboxTemplate
{
"image": "",
"command": [
""
],
"args": [
""
],
"env": {
"key": ""
},
"secrets": [
{
"secret": "projects//locations//secrets/",
"environment_variable": "",
"file_path": ""
}
],
"port": 0,
"protocol": "PROTOCOL_HTTP1",
"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
}
},
"max_concurrency": 0,
"request_timeout": "300s",
"startup_timeout": "300s",
"vpc": "projects//locations//vpcs/",
"image_pull_secret": "projects//locations//secrets/"
}

The ScalingSpec object

ScalingSpec is how many instances a sandbox runs.

Attributes

  • min_instancesint32

    Instances to keep running even with no traffic. Defaults to 0.

    Zero is scale-to-zero. One or more is a container that runs permanently, and is charged for continuously.

  • max_instancesint32

    Ceiling. Defaults to 10.

    A ceiling rather than a target, and the thing standing between a runaway trigger and a bill. It is also what sandbox.plany.cloud/max_instances is counted in, so it cannot be raised past what the project's quota allows.

  • idle_timeoutDuration

    How long an instance may sit with nothing in flight before it is released. Defaults to 15 minutes.

    Measured from the last request finishing, as counted by Plany in front of the container — the container is never asked whether it is busy.

ScalingSpec
{
"min_instances": 0,
"max_instances": 0,
"idle_timeout": "300s"
}

The SecretAttachment object

SecretAttachment makes one credential available to a container. The same shape as a game server's secret attachment, field for field.

Attributes

  • secretstringRequired

    projects/{project}/locations/{location}/secrets/{secret}, optionally with /versions/{version}.

    Unqualified resolves to the latest enabled version, so a rotation reaches this container on its next start with nothing to redeploy. Pin a version only when a revision must not follow a rotation.

  • environment_variablestringRequired

    The environment variable the container sees it as.

    Upper case, digits and underscores, not starting with a digit. The payload must be valid UTF-8 to be injected this way; a binary key is refused at launch rather than silently mangled.

  • file_pathstring

    Also write the value to this absolute path inside the container.

    For the things that will not go in a variable: a service account JSON, a TLS key, a licence file something reads by path. Written 0600, owned by the container's user, and rewritten every launch — this is the platform's copy of a value the platform holds, so a rotation cannot leave a stale one behind.

SecretAttachment
{
"secret": "projects//locations//secrets/",
"environment_variable": "",
"file_path": ""
}

ListFailedDeliveriesResponse

  • Show child attributes
    • triggerstring
    • event_typeEventType
      EVENT_TYPE_CONNECTION_OPENEDEVENT_TYPE_CONNECTION_CLOSEDEVENT_TYPE_PLAYER_JOINEDEVENT_TYPE_PLAYER_LEFTEVENT_TYPE_SERVER_READYEVENT_TYPE_SERVER_STOPPING
    • event_idstring

      The event's id, to quote when asking Plany support about it.

    • game_serverstring

      What the game server was, when the event named one.

    • reasonstring

      The last failure, in words. An HTTP status, a timeout, a sandbox that could not be started.

    • attemptsint32
    • event_timeTimestamp
    • last_attempt_timeTimestamp
  • next_page_tokenstring

ListSandboxRevisionsResponse

  • Newest first.

    Show child attributes
    • namestringIdentifier

      .../sandboxes/{sandbox}/revisions/{revision}, where the last segment is the sandbox id and an ordinal — api-00007.

    • uidstringOutput only
    • templateSandboxTemplateOutput only

      The template exactly as it was when this revision was minted.

      Show child attributes
      • imagestringRequired

        Either a repository in Plany's container registry — projects/{project}/locations/{location}/repositories/{repository} followed by :{tag} or @{digest} — or an image in an external registry, written as docker pull would take it: ghcr.io/acme/api:1.4, docker.io/library/nginx@sha256:….

        A Plany repository must be in this project and region — the registry is regional and a pull crossing a region would be bytes leaving the failure domain the sandbox is in. An external image is pulled anonymously, or with image_pull_secret when the registry needs a credential.

        A tag is resolved to a digest when the revision is minted, and the digest is what is stored and pulled. So a tag that moves does not change a running revision, which is the property that makes serving_revision a rollback rather than a hope.

      • resourcesResourceRequirementsRequired

        CPU, memory and disk, in the same terms as a game server's.

        Note CpuSpec.reserved_millis against vcpus: the gap between them is burst headroom, and it is what makes a sandbox that is idle most of the time cheap without making it slow when it is not.

      • commandrepeated string

        Override the image's ENTRYPOINT. Empty uses the image's own.

        An argument list, never a shell string: no shell is run for you.

      • argsrepeated string

        Override the image's CMD. Empty uses the image's own.

      • envmap<string, string>

        Environment variables, merged over the image's own.

        For values that are not sensitive. A credential goes in secrets, where it is fetched by the container's own identity at launch and never sits in this message, in this service's database, in an audit entry, or on a console page.

      • Credentials to make available, fetched when an instance starts by the sandbox's own identity.

        Attaching allows this sandbox to read that one secret and stops the secret being deleted while it is attached. The value is never stored in the sandbox's configuration.

      • portint32

        What the container listens on. Defaults to 8080.

        One port. A sandbox is reached through one URL, and a second listener would be a second address to name, route, certificate and bill for — which is a product decision rather than a field.

      • protocolProtocol

        How the container is spoken to.

        The ingress terminates TLS either way and speaks this to the container, so a gRPC sandbox gets prior-knowledge h2c and an HTTP one does not — a distinction that cannot be sniffed reliably and must not be guessed.

        PROTOCOL_HTTP1PROTOCOL_H2C
      • max_concurrencyint32

        How many requests one instance handles at once. Defaults to 80.

        The number the autoscaler divides by, and the one customers get wrong in both directions. Set it to 1 for a container that is not concurrency-safe; leave it high for anything that spends its time waiting on something else.

      • request_timeoutDuration

        How long a request may take before the ingress gives up. Defaults to 5 minutes, capped at 60.

        Measured from the moment the request is accepted, which includes any wait for a scaled-to-zero instance to start. That is deliberate: it is the number the caller experiences, and a timeout that excluded the cold start would be a promise about the half the platform controls.

      • startup_timeoutDuration

        How long to wait for the container to accept its first connection before calling the start failed. Defaults to 4 minutes.

      • vpcstring

        A private network the container can reach, by its addresses, as well as the internet. Empty means none.

        A sandbox is not on a customer's network: its instances run on a network Plany manages for Sandbox in the project, which nothing else is on. This connects them to one more. Each instance gets an address in the network with no default route through it — only traffic for the network's own range goes that way — and nothing on the network can open a connection to it, whatever that network's firewall rules say. So a sandbox can call a database or a game server's admin port, and cannot be called back.

        On the template, so it is part of a revision: a deploy that connects a network can be rolled back to one that did not. Requires vpc.vpcs.use on the network, which must be in the sandbox's own project and region.

      • image_pull_secretstring

        The credential an external registry needs, as a Secrets Manager secret: projects/{project}/locations/{location}/secrets/{secret}, optionally with /versions/{version}. Empty pulls anonymously. Refused for an image in Plany's own registry, which is pulled by the sandbox's identity.

        The secret's payload is JSON, {"username": "…", "password": "…"} — the pair docker login takes, where the password is usually an access token.

        Used only to pull the image when a revision is deployed. Sandbox grants the sandbox's identity roles/secretmanager.accessor on this one secret; it is never put in the container's environment.

    • image_digeststringOutput only

      The image, resolved: sha256:…. What every instance of this revision runs, whatever the tag points at now.

      For an image in another registry deployed by tag, this is set while the revision deploys, when the tag is first resolved.

    • schematicstringOutput only

      projects/{project}/locations/{location}/schematics/{schematic} — the captured disk instances boot from.

    • stateSandboxRevision.StateOutput only
      STATE_PROVISIONINGSTATE_BAKINGSTATE_READYSTATE_FAILED
    • state_reasonstringOutput only
    • created_bystringOutput only

      Who deployed it, as a principal name.

    • create_timeTimestampOutput only
    • ready_timeTimestampOutput only

      When the bake finished. Empty for a revision that never became READY.

  • next_page_tokenstring

ListSandboxTriggersResponse

  • Show child attributes
    • event_typeEventTypeRequired

      What to deliver.

      EVENT_TYPE_CONNECTION_OPENEDEVENT_TYPE_CONNECTION_CLOSEDEVENT_TYPE_PLAYER_JOINEDEVENT_TYPE_PLAYER_LEFTEVENT_TYPE_SERVER_READYEVENT_TYPE_SERVER_STOPPING
    • namestringIdentifier
    • uidstringOutput only
    • display_namestring
    • game_serverstring

      Deliver only events about this game server.

      projects/{project}/locations/{location}/gameServers/{game_server}, or empty for every game server in the project. The narrow case is the common one — a trigger for a specific fleet — and the wide one is what a project's analytics wants.

    • pathstring

      The request path events are delivered to. Defaults to /.

      So one sandbox can serve several triggers and route on the path, which is how an ordinary web framework already wants to be written.

    • STATE_ACTIVESTATE_PAUSED
    • failed_delivery_countint64Output only

      How many deliveries have been abandoned after exhausting their retries.

      Output, and the number worth putting on a console page: a trigger that stopped working is otherwise indistinguishable from one nothing has happened for.

    • last_delivery_timeTimestampOutput only
    • create_timeTimestampOutput only
    • update_timeTimestampOutput only
    • etagstringOutput only
  • next_page_tokenstring

ListSandboxesResponse

  • Show child attributes
    • templateSandboxTemplateRequired

      What to deploy.

      Writing this mints a revision. Reading it gives back what was last written, which is not necessarily what is serving: a revision that failed to bake leaves serving_revision on the one before it, and the difference between the two fields is exactly "a deploy that has not landed".

      Show child attributes
      • imagestringRequired

        Either a repository in Plany's container registry — projects/{project}/locations/{location}/repositories/{repository} followed by :{tag} or @{digest} — or an image in an external registry, written as docker pull would take it: ghcr.io/acme/api:1.4, docker.io/library/nginx@sha256:….

        A Plany repository must be in this project and region — the registry is regional and a pull crossing a region would be bytes leaving the failure domain the sandbox is in. An external image is pulled anonymously, or with image_pull_secret when the registry needs a credential.

        A tag is resolved to a digest when the revision is minted, and the digest is what is stored and pulled. So a tag that moves does not change a running revision, which is the property that makes serving_revision a rollback rather than a hope.

      • resourcesResourceRequirementsRequired

        CPU, memory and disk, in the same terms as a game server's.

        Note CpuSpec.reserved_millis against vcpus: the gap between them is burst headroom, and it is what makes a sandbox that is idle most of the time cheap without making it slow when it is not.

      • commandrepeated string

        Override the image's ENTRYPOINT. Empty uses the image's own.

        An argument list, never a shell string: no shell is run for you.

      • argsrepeated string

        Override the image's CMD. Empty uses the image's own.

      • envmap<string, string>

        Environment variables, merged over the image's own.

        For values that are not sensitive. A credential goes in secrets, where it is fetched by the container's own identity at launch and never sits in this message, in this service's database, in an audit entry, or on a console page.

      • Credentials to make available, fetched when an instance starts by the sandbox's own identity.

        Attaching allows this sandbox to read that one secret and stops the secret being deleted while it is attached. The value is never stored in the sandbox's configuration.

      • portint32

        What the container listens on. Defaults to 8080.

        One port. A sandbox is reached through one URL, and a second listener would be a second address to name, route, certificate and bill for — which is a product decision rather than a field.

      • protocolProtocol

        How the container is spoken to.

        The ingress terminates TLS either way and speaks this to the container, so a gRPC sandbox gets prior-knowledge h2c and an HTTP one does not — a distinction that cannot be sniffed reliably and must not be guessed.

        PROTOCOL_HTTP1PROTOCOL_H2C
      • max_concurrencyint32

        How many requests one instance handles at once. Defaults to 80.

        The number the autoscaler divides by, and the one customers get wrong in both directions. Set it to 1 for a container that is not concurrency-safe; leave it high for anything that spends its time waiting on something else.

      • request_timeoutDuration

        How long a request may take before the ingress gives up. Defaults to 5 minutes, capped at 60.

        Measured from the moment the request is accepted, which includes any wait for a scaled-to-zero instance to start. That is deliberate: it is the number the caller experiences, and a timeout that excluded the cold start would be a promise about the half the platform controls.

      • startup_timeoutDuration

        How long to wait for the container to accept its first connection before calling the start failed. Defaults to 4 minutes.

      • vpcstring

        A private network the container can reach, by its addresses, as well as the internet. Empty means none.

        A sandbox is not on a customer's network: its instances run on a network Plany manages for Sandbox in the project, which nothing else is on. This connects them to one more. Each instance gets an address in the network with no default route through it — only traffic for the network's own range goes that way — and nothing on the network can open a connection to it, whatever that network's firewall rules say. So a sandbox can call a database or a game server's admin port, and cannot be called back.

        On the template, so it is part of a revision: a deploy that connects a network can be rolled back to one that did not. Requires vpc.vpcs.use on the network, which must be in the sandbox's own project and region.

      • image_pull_secretstring

        The credential an external registry needs, as a Secrets Manager secret: projects/{project}/locations/{location}/secrets/{secret}, optionally with /versions/{version}. Empty pulls anonymously. Refused for an image in Plany's own registry, which is pulled by the sandbox's identity.

        The secret's payload is JSON, {"username": "…", "password": "…"} — the pair docker login takes, where the password is usually an access token.

        Used only to pull the image when a revision is deployed. Sandbox grants the sandbox's identity roles/secretmanager.accessor on this one secret; it is never put in the container's environment.

    • namestringIdentifier

      projects/{project}/locations/{location}/sandboxes/{sandbox}.

      Regional. The image is in one region's object store, the instances are on one region's hosts, and the URL names the region — a customer wanting two regions has a sandbox in each, which is the honest arrangement rather than a global name whose availability quietly depends on a region they never chose.

    • uidstringOutput only

      Server-assigned, immutable, unique for the life of the platform.

    • display_namestring

      What a person calls it. Free text.

    • descriptionstring
    • labelsmap<string, string>
    • Show child attributes
      • min_instancesint32

        Instances to keep running even with no traffic. Defaults to 0.

        Zero is scale-to-zero. One or more is a container that runs permanently, and is charged for continuously.

      • max_instancesint32

        Ceiling. Defaults to 10.

        A ceiling rather than a target, and the thing standing between a runaway trigger and a bill. It is also what sandbox.plany.cloud/max_instances is counted in, so it cannot be raised past what the project's quota allows.

      • idle_timeoutDuration

        How long an instance may sit with nothing in flight before it is released. Defaults to 15 minutes.

        Measured from the last request finishing, as counted by Plany in front of the container — the container is never asked whether it is busy.

    • ingressIngress
      INGRESS_PUBLICINGRESS_INTERNAL
    • serving_revisionstring

      The revision serving traffic, or empty to serve the newest READY one.

      Setting it is a rollback, and it is a rollback rather than a redeploy on purpose: the bytes are already baked, so it takes effect in the time it takes to start an instance rather than the time it takes to pull an image that may no longer exist under the tag it was pushed with.

    • latest_revisionstringOutput only

      The newest revision, whatever its state.

    • urlstringOutput only

      Where requests are sent, e.g. https://api-7f3a91.eu-west-2.plany.run.

      Stable for the life of the sandbox and unaffected by every deploy, rollback and scale to zero, because that is the only property that makes it safe to write into a client. Empty for INGRESS_INTERNAL.

      The suffix is not derived from the sandbox id alone: a name a stranger can guess from a project's naming convention is a public endpoint nobody published.

    • service_accountstring

      accounts/{account}/serviceAccounts/{service_account}.

      The identity the container runs as, and what it pulls its image and reads its secrets with. Left empty, Plany provisions one, binds it narrowly, and revokes it on delete — the derived-name arrangement Playpute uses, which is what lets a teardown tell an identity the platform made from one a customer attached.

      Set it to attach an identity of your own, which requires iam.serviceAccounts.actAs on it. Plany grants a customer-supplied identity nothing: whatever it can reach, it could already reach.

    • stateSandbox.StateOutput only
      STATE_PROVISIONINGSTATE_READYSTATE_FAILEDSTATE_DELETING
    • state_reasonstringOutput only

      Why the sandbox is in this state, for a person to read.

      Carries a failed revision's reason verbatim — an image that could not be resolved, a container that exited during the bake, a secret the identity could not read. Never a credential and never a presigned URL: this is rendered in the console and returned to any caller who can read the sandbox.

    • instance_countint32Output only

      How many instances are running right now.

      Zero is an ordinary answer for a healthy sandbox. has_instance_count rides beside it because "no instances" and "the ingress did not report" are different facts and only one of them is normal.

    • has_instance_countboolOutput only
    • annotationsmap<string, string>
    • generationint64Output only

      Bumped by every change to the desired state.

    • observed_generationint64Output only

      The generation that has been acted on. When it is behind generation, a change is rolling out.

    • create_timeTimestampOutput only
    • update_timeTimestampOutput only
    • etagstringOutput only
  • next_page_tokenstring

Sandbox

Sandbox is a container service: a name, a URL, and whatever is currently deployed to it.

  • templateSandboxTemplateRequired

    What to deploy.

    Writing this mints a revision. Reading it gives back what was last written, which is not necessarily what is serving: a revision that failed to bake leaves serving_revision on the one before it, and the difference between the two fields is exactly "a deploy that has not landed".

    Show child attributes
    • imagestringRequired

      Either a repository in Plany's container registry — projects/{project}/locations/{location}/repositories/{repository} followed by :{tag} or @{digest} — or an image in an external registry, written as docker pull would take it: ghcr.io/acme/api:1.4, docker.io/library/nginx@sha256:….

      A Plany repository must be in this project and region — the registry is regional and a pull crossing a region would be bytes leaving the failure domain the sandbox is in. An external image is pulled anonymously, or with image_pull_secret when the registry needs a credential.

      A tag is resolved to a digest when the revision is minted, and the digest is what is stored and pulled. So a tag that moves does not change a running revision, which is the property that makes serving_revision a rollback rather than a hope.

    • resourcesResourceRequirementsRequired

      CPU, memory and disk, in the same terms as a game server's.

      Note CpuSpec.reserved_millis against vcpus: the gap between them is burst headroom, and it is what makes a sandbox that is idle most of the time cheap without making it slow when it is not.

      Show child attributes
    • commandrepeated string

      Override the image's ENTRYPOINT. Empty uses the image's own.

      An argument list, never a shell string: no shell is run for you.

    • argsrepeated string

      Override the image's CMD. Empty uses the image's own.

    • envmap<string, string>

      Environment variables, merged over the image's own.

      For values that are not sensitive. A credential goes in secrets, where it is fetched by the container's own identity at launch and never sits in this message, in this service's database, in an audit entry, or on a console page.

    • Credentials to make available, fetched when an instance starts by the sandbox's own identity.

      Attaching allows this sandbox to read that one secret and stops the secret being deleted while it is attached. The value is never stored in the sandbox's configuration.

      Show child attributes
      • secretstringRequired

        projects/{project}/locations/{location}/secrets/{secret}, optionally with /versions/{version}.

        Unqualified resolves to the latest enabled version, so a rotation reaches this container on its next start with nothing to redeploy. Pin a version only when a revision must not follow a rotation.

      • environment_variablestringRequired

        The environment variable the container sees it as.

        Upper case, digits and underscores, not starting with a digit. The payload must be valid UTF-8 to be injected this way; a binary key is refused at launch rather than silently mangled.

      • file_pathstring

        Also write the value to this absolute path inside the container.

        For the things that will not go in a variable: a service account JSON, a TLS key, a licence file something reads by path. Written 0600, owned by the container's user, and rewritten every launch — this is the platform's copy of a value the platform holds, so a rotation cannot leave a stale one behind.

    • portint32

      What the container listens on. Defaults to 8080.

      One port. A sandbox is reached through one URL, and a second listener would be a second address to name, route, certificate and bill for — which is a product decision rather than a field.

    • protocolProtocol

      How the container is spoken to.

      The ingress terminates TLS either way and speaks this to the container, so a gRPC sandbox gets prior-knowledge h2c and an HTTP one does not — a distinction that cannot be sniffed reliably and must not be guessed.

      PROTOCOL_HTTP1PROTOCOL_H2C
    • max_concurrencyint32

      How many requests one instance handles at once. Defaults to 80.

      The number the autoscaler divides by, and the one customers get wrong in both directions. Set it to 1 for a container that is not concurrency-safe; leave it high for anything that spends its time waiting on something else.

    • request_timeoutDuration

      How long a request may take before the ingress gives up. Defaults to 5 minutes, capped at 60.

      Measured from the moment the request is accepted, which includes any wait for a scaled-to-zero instance to start. That is deliberate: it is the number the caller experiences, and a timeout that excluded the cold start would be a promise about the half the platform controls.

    • startup_timeoutDuration

      How long to wait for the container to accept its first connection before calling the start failed. Defaults to 4 minutes.

    • vpcstring

      A private network the container can reach, by its addresses, as well as the internet. Empty means none.

      A sandbox is not on a customer's network: its instances run on a network Plany manages for Sandbox in the project, which nothing else is on. This connects them to one more. Each instance gets an address in the network with no default route through it — only traffic for the network's own range goes that way — and nothing on the network can open a connection to it, whatever that network's firewall rules say. So a sandbox can call a database or a game server's admin port, and cannot be called back.

      On the template, so it is part of a revision: a deploy that connects a network can be rolled back to one that did not. Requires vpc.vpcs.use on the network, which must be in the sandbox's own project and region.

    • image_pull_secretstring

      The credential an external registry needs, as a Secrets Manager secret: projects/{project}/locations/{location}/secrets/{secret}, optionally with /versions/{version}. Empty pulls anonymously. Refused for an image in Plany's own registry, which is pulled by the sandbox's identity.

      The secret's payload is JSON, {"username": "…", "password": "…"} — the pair docker login takes, where the password is usually an access token.

      Used only to pull the image when a revision is deployed. Sandbox grants the sandbox's identity roles/secretmanager.accessor on this one secret; it is never put in the container's environment.

  • namestringIdentifier

    projects/{project}/locations/{location}/sandboxes/{sandbox}.

    Regional. The image is in one region's object store, the instances are on one region's hosts, and the URL names the region — a customer wanting two regions has a sandbox in each, which is the honest arrangement rather than a global name whose availability quietly depends on a region they never chose.

  • uidstringOutput only

    Server-assigned, immutable, unique for the life of the platform.

  • display_namestring

    What a person calls it. Free text.

  • descriptionstring
  • labelsmap<string, string>
  • Show child attributes
    • min_instancesint32

      Instances to keep running even with no traffic. Defaults to 0.

      Zero is scale-to-zero. One or more is a container that runs permanently, and is charged for continuously.

    • max_instancesint32

      Ceiling. Defaults to 10.

      A ceiling rather than a target, and the thing standing between a runaway trigger and a bill. It is also what sandbox.plany.cloud/max_instances is counted in, so it cannot be raised past what the project's quota allows.

    • idle_timeoutDuration

      How long an instance may sit with nothing in flight before it is released. Defaults to 15 minutes.

      Measured from the last request finishing, as counted by Plany in front of the container — the container is never asked whether it is busy.

  • ingressIngress
    INGRESS_PUBLICINGRESS_INTERNAL
  • serving_revisionstring

    The revision serving traffic, or empty to serve the newest READY one.

    Setting it is a rollback, and it is a rollback rather than a redeploy on purpose: the bytes are already baked, so it takes effect in the time it takes to start an instance rather than the time it takes to pull an image that may no longer exist under the tag it was pushed with.

  • latest_revisionstringOutput only

    The newest revision, whatever its state.

  • urlstringOutput only

    Where requests are sent, e.g. https://api-7f3a91.eu-west-2.plany.run.

    Stable for the life of the sandbox and unaffected by every deploy, rollback and scale to zero, because that is the only property that makes it safe to write into a client. Empty for INGRESS_INTERNAL.

    The suffix is not derived from the sandbox id alone: a name a stranger can guess from a project's naming convention is a public endpoint nobody published.

  • service_accountstring

    accounts/{account}/serviceAccounts/{service_account}.

    The identity the container runs as, and what it pulls its image and reads its secrets with. Left empty, Plany provisions one, binds it narrowly, and revokes it on delete — the derived-name arrangement Playpute uses, which is what lets a teardown tell an identity the platform made from one a customer attached.

    Set it to attach an identity of your own, which requires iam.serviceAccounts.actAs on it. Plany grants a customer-supplied identity nothing: whatever it can reach, it could already reach.

  • stateSandbox.StateOutput only
    STATE_PROVISIONINGSTATE_READYSTATE_FAILEDSTATE_DELETING
  • state_reasonstringOutput only

    Why the sandbox is in this state, for a person to read.

    Carries a failed revision's reason verbatim — an image that could not be resolved, a container that exited during the bake, a secret the identity could not read. Never a credential and never a presigned URL: this is rendered in the console and returned to any caller who can read the sandbox.

  • instance_countint32Output only

    How many instances are running right now.

    Zero is an ordinary answer for a healthy sandbox. has_instance_count rides beside it because "no instances" and "the ingress did not report" are different facts and only one of them is normal.

  • has_instance_countboolOutput only
  • annotationsmap<string, string>
  • generationint64Output only

    Bumped by every change to the desired state.

  • observed_generationint64Output only

    The generation that has been acted on. When it is behind generation, a change is rolling out.

  • create_timeTimestampOutput only
  • update_timeTimestampOutput only
  • etagstringOutput only

SandboxRevision

SandboxRevision is one immutable deployment.

Created by writing Sandbox.template, never directly. Nothing about it can be edited: a revision that could change would make serving_revision a rollback to something other than what was there, which is worse than no rollback at all.

  • namestringIdentifier

    .../sandboxes/{sandbox}/revisions/{revision}, where the last segment is the sandbox id and an ordinal — api-00007.

  • uidstringOutput only
  • templateSandboxTemplateOutput only

    The template exactly as it was when this revision was minted.

    Show child attributes
    • imagestringRequired

      Either a repository in Plany's container registry — projects/{project}/locations/{location}/repositories/{repository} followed by :{tag} or @{digest} — or an image in an external registry, written as docker pull would take it: ghcr.io/acme/api:1.4, docker.io/library/nginx@sha256:….

      A Plany repository must be in this project and region — the registry is regional and a pull crossing a region would be bytes leaving the failure domain the sandbox is in. An external image is pulled anonymously, or with image_pull_secret when the registry needs a credential.

      A tag is resolved to a digest when the revision is minted, and the digest is what is stored and pulled. So a tag that moves does not change a running revision, which is the property that makes serving_revision a rollback rather than a hope.

    • resourcesResourceRequirementsRequired

      CPU, memory and disk, in the same terms as a game server's.

      Note CpuSpec.reserved_millis against vcpus: the gap between them is burst headroom, and it is what makes a sandbox that is idle most of the time cheap without making it slow when it is not.

      Show child attributes
    • commandrepeated string

      Override the image's ENTRYPOINT. Empty uses the image's own.

      An argument list, never a shell string: no shell is run for you.

    • argsrepeated string

      Override the image's CMD. Empty uses the image's own.

    • envmap<string, string>

      Environment variables, merged over the image's own.

      For values that are not sensitive. A credential goes in secrets, where it is fetched by the container's own identity at launch and never sits in this message, in this service's database, in an audit entry, or on a console page.

    • Credentials to make available, fetched when an instance starts by the sandbox's own identity.

      Attaching allows this sandbox to read that one secret and stops the secret being deleted while it is attached. The value is never stored in the sandbox's configuration.

      Show child attributes
      • secretstringRequired

        projects/{project}/locations/{location}/secrets/{secret}, optionally with /versions/{version}.

        Unqualified resolves to the latest enabled version, so a rotation reaches this container on its next start with nothing to redeploy. Pin a version only when a revision must not follow a rotation.

      • environment_variablestringRequired

        The environment variable the container sees it as.

        Upper case, digits and underscores, not starting with a digit. The payload must be valid UTF-8 to be injected this way; a binary key is refused at launch rather than silently mangled.

      • file_pathstring

        Also write the value to this absolute path inside the container.

        For the things that will not go in a variable: a service account JSON, a TLS key, a licence file something reads by path. Written 0600, owned by the container's user, and rewritten every launch — this is the platform's copy of a value the platform holds, so a rotation cannot leave a stale one behind.

    • portint32

      What the container listens on. Defaults to 8080.

      One port. A sandbox is reached through one URL, and a second listener would be a second address to name, route, certificate and bill for — which is a product decision rather than a field.

    • protocolProtocol

      How the container is spoken to.

      The ingress terminates TLS either way and speaks this to the container, so a gRPC sandbox gets prior-knowledge h2c and an HTTP one does not — a distinction that cannot be sniffed reliably and must not be guessed.

      PROTOCOL_HTTP1PROTOCOL_H2C
    • max_concurrencyint32

      How many requests one instance handles at once. Defaults to 80.

      The number the autoscaler divides by, and the one customers get wrong in both directions. Set it to 1 for a container that is not concurrency-safe; leave it high for anything that spends its time waiting on something else.

    • request_timeoutDuration

      How long a request may take before the ingress gives up. Defaults to 5 minutes, capped at 60.

      Measured from the moment the request is accepted, which includes any wait for a scaled-to-zero instance to start. That is deliberate: it is the number the caller experiences, and a timeout that excluded the cold start would be a promise about the half the platform controls.

    • startup_timeoutDuration

      How long to wait for the container to accept its first connection before calling the start failed. Defaults to 4 minutes.

    • vpcstring

      A private network the container can reach, by its addresses, as well as the internet. Empty means none.

      A sandbox is not on a customer's network: its instances run on a network Plany manages for Sandbox in the project, which nothing else is on. This connects them to one more. Each instance gets an address in the network with no default route through it — only traffic for the network's own range goes that way — and nothing on the network can open a connection to it, whatever that network's firewall rules say. So a sandbox can call a database or a game server's admin port, and cannot be called back.

      On the template, so it is part of a revision: a deploy that connects a network can be rolled back to one that did not. Requires vpc.vpcs.use on the network, which must be in the sandbox's own project and region.

    • image_pull_secretstring

      The credential an external registry needs, as a Secrets Manager secret: projects/{project}/locations/{location}/secrets/{secret}, optionally with /versions/{version}. Empty pulls anonymously. Refused for an image in Plany's own registry, which is pulled by the sandbox's identity.

      The secret's payload is JSON, {"username": "…", "password": "…"} — the pair docker login takes, where the password is usually an access token.

      Used only to pull the image when a revision is deployed. Sandbox grants the sandbox's identity roles/secretmanager.accessor on this one secret; it is never put in the container's environment.

  • image_digeststringOutput only

    The image, resolved: sha256:…. What every instance of this revision runs, whatever the tag points at now.

    For an image in another registry deployed by tag, this is set while the revision deploys, when the tag is first resolved.

  • schematicstringOutput only

    projects/{project}/locations/{location}/schematics/{schematic} — the captured disk instances boot from.

  • stateSandboxRevision.StateOutput only
    STATE_PROVISIONINGSTATE_BAKINGSTATE_READYSTATE_FAILED
  • state_reasonstringOutput only
  • created_bystringOutput only

    Who deployed it, as a principal name.

  • create_timeTimestampOutput only
  • ready_timeTimestampOutput only

    When the bake finished. Empty for a revision that never became READY.

SandboxTrigger

SandboxTrigger runs a sandbox when something happens on the platform.

The half of this product a generic cloud cannot offer. Plany already knows when a player joined, when a server went quiet and when a connection arrived at something that was not running, because it learned all three from the game server's own output and from the socket its connection proxy accepted. A trigger is a customer's code attached to one of those facts.

Delivery is a request to the sandbox, on the same path an HTTP caller takes: the same wake, the same concurrency accounting, the same meter. A second way in would be a second place for each of those to be wrong.

  • event_typeEventTypeRequired

    What to deliver.

    EVENT_TYPE_CONNECTION_OPENEDEVENT_TYPE_CONNECTION_CLOSEDEVENT_TYPE_PLAYER_JOINEDEVENT_TYPE_PLAYER_LEFTEVENT_TYPE_SERVER_READYEVENT_TYPE_SERVER_STOPPING
  • namestringIdentifier
  • uidstringOutput only
  • display_namestring
  • game_serverstring

    Deliver only events about this game server.

    projects/{project}/locations/{location}/gameServers/{game_server}, or empty for every game server in the project. The narrow case is the common one — a trigger for a specific fleet — and the wide one is what a project's analytics wants.

  • pathstring

    The request path events are delivered to. Defaults to /.

    So one sandbox can serve several triggers and route on the path, which is how an ordinary web framework already wants to be written.

  • STATE_ACTIVESTATE_PAUSED
  • failed_delivery_countint64Output only

    How many deliveries have been abandoned after exhausting their retries.

    Output, and the number worth putting on a console page: a trigger that stopped working is otherwise indistinguishable from one nothing has happened for.

  • last_delivery_timeTimestampOutput only
  • create_timeTimestampOutput only
  • update_timeTimestampOutput only
  • etagstringOutput only

Enums

EventType

EventType is something that happens on the platform which a sandbox can be started by: a connection, a player joining or leaving, a server becoming ready or stopping.

ValueDescription
EVENT_TYPE_UNSPECIFIED
EVENT_TYPE_CONNECTION_OPENEDSomebody connected to a game server. The earliest signal the platform has, and the only one that exists for a server that is not running: the connection proxy accepts the socket before anything has started. `woke_server` on the delivered event says whether this connection is what started it.
EVENT_TYPE_CONNECTION_CLOSEDA connection ended.
EVENT_TYPE_PLAYER_JOINEDA player joined, identified by name. Later than the connection — it comes from the server's own output, so the server is by definition running — and richer, because it carries who. The delivered event carries `active_player_count` after applying it.
EVENT_TYPE_PLAYER_LEFTA player left. Note what this does not promise. Three of five end reasons mean nobody announced a departure and the platform inferred one, and `END_REASON_EXPIRED` in particular is a ceiling rather than a measurement. The reason rides on the delivered event so a customer's code can tell them apart.
EVENT_TYPE_SERVER_READYA game server finished starting and is accepting players.
EVENT_TYPE_SERVER_STOPPINGA game server is shutting down.

Ingress

Ingress says who can reach a sandbox.

ValueDescription
INGRESS_UNSPECIFIEDPublic, which is what a sandbox with an HTTP surface is.
INGRESS_PUBLICReachable at `url` from the internet, subject to whatever the container itself checks. Deliberately not authenticated by the platform. A sandbox is very often a webhook receiver or a game client's backend, and neither can present a Plany credential. `sandbox.sandboxes.invoke` exists for the other case and is enforced only on `INGRESS_INTERNAL`.
INGRESS_INTERNALNo public address. Reachable by triggers, and by a caller holding `sandbox.sandboxes.invoke` on this sandbox. The right answer for a sandbox that only ever reacts to platform events: it has no URL to leak, no unauthenticated surface, and no reason to have one.

Protocol

Protocol is what the ingress speaks to a container.

ValueDescription
PROTOCOL_UNSPECIFIEDHTTP/1.1.
PROTOCOL_HTTP1
PROTOCOL_H2CHTTP/2 with no TLS, which is what a gRPC server expects. Requests reach your sandbox's URL over TLS either way.

Sandbox.State

ValueDescription
STATE_UNSPECIFIED
STATE_PROVISIONINGA revision is being baked and nothing serves yet. The first deploy only. A *later* deploy leaves the sandbox READY on its previous revision throughout, which is the difference between a bad push costing a customer their next release and costing them their service.
STATE_READYServing. Instances may still be zero — a sandbox at rest is ready, and rendering it as anything else would teach customers that the normal state of a scale-to-zero service looks broken.
STATE_FAILEDNo revision can serve. `state_reason` says which one failed and why.
STATE_DELETINGThe row is gone from every list; instances and identity are being torn down.

SandboxRevision.State

ValueDescription
STATE_UNSPECIFIED
STATE_PROVISIONINGAccepted; the image is being resolved.
STATE_BAKINGThe image is being pulled and prepared. Once per revision. Every instance afterwards starts from the prepared copy, which is why a cold start is seconds rather than an image pull.
STATE_READYStartable.
STATE_FAILED`state_reason` says why. A failed revision leaves the previous one serving.

SandboxTrigger.State

ValueDescription
STATE_UNSPECIFIED
STATE_ACTIVE
STATE_PAUSEDNot delivering. A customer's own pause, and the thing to reach for when a sandbox is failing every delivery rather than deleting the trigger and losing its configuration.