PLATINUM DOCS

Sandboxes

Create, list, rename, stop, start, resize, archive, and delete sandboxes.

A sandbox is one isolated microVM, created from a template. Use sandboxes to run untrusted code, agents, or full dev workloads. Manage them in the dashboard or via the API (reference).

Sandboxes are persistent by default. stop keeps the disk and the spec, and start brings the sandbox back. A sandbox is removed only when you delete it, or automatically for ephemeral sandboxes. See Lifecycle.

Create

import { Platinum } from "@platinum-dev/sdk";

const client = new Platinum({ url: process.env.PT_API_URL!, token: process.env.PT_TOKEN! });
const sbx = await client.sandboxes.create(
  { template: "pt-base", cpu: 2, ram_mb: 1024 },
  { waitForRunning: true },
);
import os
from platinum import Platinum

client = Platinum(token=os.environ["PT_TOKEN"], api_url=os.environ["PT_API_URL"])
sbx = client.sandboxes.create(template="pt-base", cpu=2, ram_mb=1024,
                              wait_for_running=True)
pt sandbox create -t pt-base --cpu 2 --ram 1024
curl -sS -X POST "$PT_API_URL/v1/sandboxes?wait_for_state=running" \
  -H "Authorization: Bearer $PT_TOKEN" -H "Content-Type: application/json" \
  -d '{"template":"pt-base","cpu":2,"ram_mb":1024}'

Common create fields:

FieldDefaultWhat it does
templatept-baseTemplate to boot from. Or pass image for an inline build. Set one, not both.
typepersistentpersistent never auto-stops. ephemeral applies the auto-lifecycle defaults. See Retention.
cpu · ram_mb · disk_gbtemplate defaultsResource shape. Template minimums are enforced.
env{}Environment variables set in the sandbox at boot.
exposePorts to publish in the create call. See Networking.
regiontemplate's, else anyPlacement. See Regions.
metadata{}Arbitrary JSON (≤ 16 KiB), echoed back on reads.

The create body also accepts ssh_keys (Terminal), volume_ids (Volumes), and gpus. Full options, with types and validation rules, are in the reference for your surface:

Safe retries

POST /v1/sandboxes accepts an optional Idempotency-Key header. Send the same key on a retry and you get the original sandbox back instead of a second VM (and a second billing stream). Without the header nothing changes: the request behaves exactly as it always has.

KEY=$(uuidgen)
curl -sS -X POST "$PT_API_URL/v1/sandboxes" \
  -H "Authorization: Bearer $PT_TOKEN" -H "Content-Type: application/json" \
  -H "Idempotency-Key: $KEY" \
  -d '{"template":"pt-base","cpu":2,"ram_mb":1024}'

Rules worth knowing:

  • The key must be 8-255 characters. Generate a fresh one per logical create (a UUID is ideal); do not reuse a key across different requests.
  • A replay returns 200 with "replayed": true and the original id, where the first call returned 201.
  • The key is bound to the request: the same key with a different body, or a different wait_for_state / wait_timeout_ms, returns 422 idempotency_key_reused.
  • Keys are scoped to the API key (or user) that created them, and expire after 24 hours.
  • A replay does not return the exposed array, because preview tokens are not stored. Call POST /v1/sandboxes/:id/expose again if you need the URLs (it is idempotent).
  • If the sandbox was deleted or failed to start, the key is dropped and the retry creates a fresh sandbox rather than handing back a dead one.
  • A retry still counts against the spawn rate limit, so a retry storm can still get a 429.
  • The header is honoured only on create. POST /:id/clone and POST /:id/fork ignore it, and the vendor-compatibility endpoints cannot send it.

Named sandboxes are already duplicate-safe.

name is unique per org across live sandboxes, so a retried create with the same name returns 409 name_taken instead of making a second box.

Lifecycle

Only running bills compute. stopped bills local storage. archived bills nothing. See Billing.

Retention

type sets when a sandbox auto-stops, auto-archives, and auto-deletes:

typeidle → stopstopped → archivestopped → delete
persistentnever7 daysnever
ephemeral15 min7 days30 days

These are defaults. Pass auto_stop_minutes, auto_archive_days, or auto_delete_days at create to override one. 0 disables that timer.

List

const { rows } = await client.sandboxes.list({ state: "running", limit: 100 });
for await (const s of client.sandboxes.iterate({ state: "running" })) { /* auto-paginate */ }
rows = client.sandboxes.list(state="running", limit=100)["rows"]
for s in client.sandboxes.iter(state="running"):
    ...
pt sandbox list --state running --limit 100
curl "$PT_API_URL/v1/sandboxes?state=running&limit=100" -H "Authorization: Bearer $PT_TOKEN"

Query params: state, limit (default 50, max 200), offset, and paginated=true. Results are org-scoped.

Parent and child sandboxes

A sandbox can be the child of another sandbox. Set parent_id at create time. A sandbox without a parent_id is a top-level sandbox.

Use this to group related sandboxes. An agent working inside a sandbox can create child sandboxes and manage only those.

const child = await client.sandboxes.create({ template: "pt-base", parent_id: parent.id });
const kids = await client.sandboxes.children(parent.id);
child = client.sandboxes.create(template="pt-base", parent_id=parent["id"])
kids = client.sandboxes.children(parent["id"])
pt sandbox create -t pt-base --parent sbx_01K…
pt sandbox children sbx_01K…
curl -X POST "$PT_API_URL/v1/sandboxes" -H "Authorization: Bearer $PT_TOKEN" \
  -H 'Content-Type: application/json' -d '{"template":"pt-base","parent_id":"sbx_01K…"}'
curl "$PT_API_URL/v1/sandboxes/$SBX/children" -H "Authorization: Bearer $PT_TOKEN"

The parent must be running and in the same organization. A missing parent returns 404.

parent_id is set once at create. You cannot move a sandbox to a different parent.

List sandboxes by position in the tree with parent_id:

const children = await client.sandboxes.list({ parent_id: parent.id });
const roots = await client.sandboxes.list({ rootsOnly: true });
children = client.sandboxes.list(parent_id=parent["id"])["rows"]
roots = client.sandboxes.list(roots_only=True)["rows"]
pt sandbox list --parent sbx_01K…
pt sandbox list --roots
curl "$PT_API_URL/v1/sandboxes?parent_id=$SBX" -H "Authorization: Bearer $PT_TOKEN"
curl "$PT_API_URL/v1/sandboxes?parent_id=none" -H "Authorization: Bearer $PT_TOKEN"

The default list is unchanged. Without parent_id you get every sandbox in the organization.

Warning: delete children before the parent. Deleting a parent that still has live children returns 409. The response names the blocking children.

Deleting a sandbox never deletes its children.

Get

const s = await client.sandboxes.get("sbx_01K…");
const sbx = await client.sandboxes.connect("sbx_01K…");
s = client.sandboxes.get("sbx_01K…")
sbx = client.sandboxes.connect("sbx_01K…")
pt sandbox get sbx_01K…
curl "$PT_API_URL/v1/sandboxes/$SBX" -H "Authorization: Bearer $PT_TOKEN"

get returns the record; connect returns a handle for further calls. Live metrics and billed usage are separate endpoints: GET /v1/sandboxes/:id/metrics and GET /v1/sandboxes/:id/usage.

Rename

Pass null (Python: None) to clear the name.

await sbx.rename("ci-runner-2");
sbx.rename("ci-runner-2")
pt sandbox rename $SBX ci-runner-2
# clear the name:
pt sandbox rename $SBX --clear
curl -X PATCH "$PT_API_URL/v1/sandboxes/$SBX" -H "Authorization: Bearer $PT_TOKEN" \
  -H "Content-Type: application/json" -d '{"name":"ci-runner-2"}'

Stop and start

stop saves memory where it can, so start resumes with processes intact. start also unarchives an archived sandbox first.

await sbx.stop({ waitForStopped: true });
await sbx.start({ waitForRunning: true });
sbx.stop(wait_for_stopped=True)
sbx.start(wait_for_running=True)
pt sandbox stop sbx_01K…
pt sandbox start sbx_01K…
curl -X POST "$PT_API_URL/v1/sandboxes/$SBX/stop?wait_for_state=stopped" -H "Authorization: Bearer $PT_TOKEN"
curl -X POST "$PT_API_URL/v1/sandboxes/$SBX/start?wait_for_state=running" -H "Authorization: Bearer $PT_TOKEN"

Resize

Resize a stopped sandbox only. disk_gb can only grow. The resize cold-boots the sandbox: the disk survives, in-memory state does not.

await sbx.resize({ cpu: 4, ram_mb: 8192, disk_gb: 8 });
sbx.resize(cpu=4, ram_mb=8192, disk_gb=8)
pt sandbox resize sbx_01K… --cpu 4 --ram 8192 --disk 8
curl -X POST "$PT_API_URL/v1/sandboxes/$SBX/resize" -H "Authorization: Bearer $PT_TOKEN" \
  -H "Content-Type: application/json" -d '{"cpu":4,"ram_mb":8192,"disk_gb":8}'

Archive

Archive a stopped sandbox only. Archiving moves the disk to cold storage and frees the local disk. An archived sandbox bills nothing. start restores it; there is no separate unarchive endpoint.

await sbx.archive();
sbx.archive()
pt sandbox archive $SBX
curl -X POST "$PT_API_URL/v1/sandboxes/$SBX/archive" -H "Authorization: Bearer $PT_TOKEN"

Delete

Delete removes the sandbox and its disk. The call returns immediately and tears down in the background.

await sbx.delete();
sbx.delete()
pt sandbox rm sbx_01K…
curl -X DELETE "$PT_API_URL/v1/sandboxes/$SBX" -H "Authorization: Bearer $PT_TOKEN"

Snapshot and fork

A running sandbox also supports snapshot, restore, clone, fork, and off-host backup. See Snapshots.