PLATINUM DOCS
SDKs

Python SDK

The typed Python client for Platinum, in sync and async form.

platinum-sdk is the typed Python client for the Platinum API. It covers sandboxes, templates, volumes, and webhooks. Use it in server-side Python; the TypeScript SDK has the same surface.

Install

pip install platinum-sdk

The package requires Python 3.9+ and has one dependency (httpx). It is fully typed (PEP 561).

Quickstart

from platinum import Platinum

client = Platinum(token="pt_live_…", api_url="https://api.platinum.dev")

sbx = client.sandboxes.create(
    template="pt-base",
    expose=[{"port": 8000, "public": True}],
    wait_for_running=True,
)
print(sbx.exposed_url(8000))

print(sbx.exec(["python3", "-c", "print(2 + 2)"]).check().stdout)
sbx.sh("echo $HOME && ls /")

sbx.delete()

Client

The timeout argument bounds each request. The default is 60 seconds. Close the client with client.close().

Async

AsyncPlatinum mirrors Platinum one to one over httpx.AsyncClient. The methods and arguments are the same, awaited. Everything below applies to both clients.

from platinum import AsyncPlatinum

async with AsyncPlatinum() as client:
    sbx = await client.sandboxes.create(template="pt-base", wait_for_running=True)
    print((await sbx.exec(["uname", "-a"])).stdout)

    async for row in client.sandboxes.iter(state="running"):
        print(row["id"])

    await sbx.delete()

Close a standalone async client with await client.aclose(). The async client builds templates with await client.templates.build(tpl, name). The sync client uses tpl.build(client, name).

Sandboxes

create returns a Sandbox handle. Pass template (a prebuilt name or id) or image (a Template builder). Do not pass both.

sbx = client.sandboxes.create(
    template="pt-base",
    cpu=2, ram_mb=2048,
    env={"STAGE": "prod"},
    wait_for_running=True,
)

create also accepts lifecycle timers, ssh_keys, volume_ids, gpus, and metadata. The first inline image build can exceed the default wait. Raise wait_timeout_ms for it. See regions, volumes, and networking.

Each expose entry is {"port": int, "public": bool}. Read the URLs from the handle with sbx.exposed_url(port).

Find and list:

MethodReturns
sandboxes.get(id)raw row (dict)
sandboxes.connect(id)hydrated Sandbox handle (404 raises NotFoundError)
sandboxes.list(limit=50, offset=0, state=None){rows, total, has_more}
sandboxes.iter(state=None, page_size=100)generator over every page
sandboxes.rename(id, name)set or clear the name (None clears; 409 if taken)
sandboxes.delete(id)delete (also sbx.delete())

Lifecycle

The Sandbox handle carries id, name, and exposed (ports exposed at create). See sandboxes.

Run commands

The call returns output when the command exits. See exec.

sbx.exec(["ls", "-la", "/"])           # argv, direct execve
sbx.exec("echo hi && ls")              # string, wrapped in sh -c
sbx.sh("for i in 1 2 3; do echo $i; done")
sbx.run_code("print(sum(range(10)))")  # lang defaults to the sandbox language
MethodNotes
exec(cmd, timeout_ms=30_000)argv list or string; returns ExecResult; .check() raises on nonzero exit
sh(script, timeout_ms=30_000)sugar for exec(["sh", "-c", script])
run_code(code, lang=None, timeout_ms=30_000)lang: python, bash, node, or sh; defaults to the create-time language

Files

sbx.files reads and writes anywhere in the guest filesystem. See filesystem.

sbx.files.write("/workspace/app.py", "print('hi')")
data = sbx.files.read("/workspace/app.py")   # bytes

for ev in sbx.files.watch("/workspace", max_seconds=60):
    if ev["event"] == "change":
        print(ev["data"]["type"], ev["data"]["path"])
MethodNotes
read(path) / write(path, body)read returns bytes; body is str or bytes
delete(path, recurse=False)
list(path) / stat(path) / mkdir(path)
exists(path)returns False instead of raising
find(path, pattern, max=None)filename glob; returns matched paths
grep(path, pattern, max=None)content search; returns file:line:content lines
replace(path, find, replace, glob="*")in-place sed -E across files
watch(path, max_seconds=300)generator of change events; the server ends it after max_seconds

Networking

Expose a port through the edge proxy. See networking.

r = sbx.expose(8000)             # private, HMAC-token URL
print(r["url"])
sbx.expose(8000, public=True)    # public URL, no token
MethodNotes
expose(port, public=None, ttl_seconds=None)returns {url, …}; public=True drops the token
unexpose(port)tear the port down; kills all tokens ever minted
exposed_url(port)URL of an already-exposed port, else None
revoke_expose_token(token, reason=None)kill one leaked token; the port stays up
set_egress_policy(mode, cidrs)mode="allow": block the listed CIDRs. mode="deny": allow only the listed CIDRs
add_ssh_keys(keys)replace the full /root/.ssh/authorized_keys set

Metrics: sbx.metrics() returns allocated resources plus live cpu/mem/disk. Live values are None until the first sample. sbx.usage(since_ms=None) returns cumulative billed usage.

Snapshots and forking

Disk snapshots and clones. See snapshots.

MethodNotes
snapshot(scrub_env=, clear_tmp=, clear_ssh_keys=)point-in-time disk snapshot; the sandbox must run
list_snapshots() / delete_snapshot(id)snapshots of this sandbox
restore(snapshot_id=None)roll this sandbox back (latest when omitted)
clone(snapshot_id=None)boot a new sandbox from a snapshot; the source keeps running
fork()snapshot and clone in one call
archive() / backup() / restore_from_backup()cold storage

Templates

Template builds a custom image. Use it inline in create(image=...), or build a named, reusable template. See templates and the declarative builder.

from platinum import Platinum, Template

client = Platinum()
image = (Template.from_python_image("3.12-slim")
                 .pip_install(["fastapi", "uvicorn"])
                 .workdir("/app"))

# Boot straight from the spec (cache-hit on repeat)
sbx = client.sandboxes.create(image=image, wait_for_running=True, wait_timeout_ms=600_000)

# Or build a reusable named template
result = image.build(client, name="my-agent")

Start from from_image(), or a shortcut for Python, Node, Bun, Ubuntu, Debian, and Alpine. Chain steps to install packages, copy files, and set env, workdir, user, and entrypoint. Full step list: declarative builder.

build() waits until the build finishes. Pass wait_ms=0 to skip the wait and poll later.

Other client methods

Method
client.volumescreate / list / get / delete / attach / detach (see volumes)
client.webhookscreate / list / update / delete / test / deliveries / retry_delivery (see webhooks)
client.templatesbuild / list / get / delete (see templates)
client.regions.list()regions that can place a sandbox now
client.me()identity and role of the current API key
client.health.check()API health ({status, ts})
client.request(method, path, ...)escape hatch for any endpoint, with typed errors and timeouts
client.close()close the HTTP client

API keys, audit, and billing have no typed wrapper. Call client.request() with routes from the API reference. The interactive terminal is a raw WebSocket; connect to it directly.

Errors

Every SDK exception subclasses PlatinumError. It carries .status, .body, and .code (the API's machine-readable code, also part of str(err)).

The SDK does not retry. Handle RateLimitError yourself.

import time
from platinum import ConflictError, NotFoundError, RateLimitError

try:
    sbx.exec("true")
except NotFoundError:
    ...                                   # gone
except ConflictError:
    sbx.start(wait_for_running=True)      # wrong state, e.g. stopped
except RateLimitError as e:
    time.sleep(e.retry_after_seconds or 1)

Branch on .code for conflict detail, for example sandbox_not_running.