PLATINUM DOCS
SDKs

TypeScript SDK

TypeScript client for the Platinum API. Create sandboxes, run commands, manage files, and build templates.

@platinum-dev/sdk is the TypeScript client for the Platinum API. This page is the full client reference. For a first walkthrough, read the quickstart.

Install

npm install @platinum-dev/sdk

Requires Node 18 or later, or Bun. Use it server-side.

Create a client

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

const client = new Platinum({
  url: "https://api.platinum.dev",
  token: process.env.PT_TOKEN,
});

timeoutMs sets the per-request deadline. The default is 60 seconds.

Create a sandbox and run a command

waitForRunning: true holds the create call server-side until the sandbox is running. The client does not poll.

const sbx = await client.sandboxes.create(
  { template: "pt-base", env: { GREETING: "hi" } },
  { waitForRunning: true },
);

const r = await sbx.exec(["uname", "-a"]);
console.log(r.exit_code, r.stdout);
r.check();

await sbx.runCode("print(2+2)", { lang: "python" });
await sbx.delete();

Create options

Pass template or image (declarative builder), never both. A first inline build can outlast the default 60 second wait. Raise waitTimeoutMs for it.

const sbx = await client.sandboxes.create(
  {
    template: "pt-base",              // or image: <Template> for an inline build
    type: "ephemeral",               // persistent (default) | ephemeral
    region: "fr-par",
    cpu: 2,
    ram_mb: 2048,
    disk_gb: 10,
    env: { OPENAI_API_KEY: "…" },
    expose: [{ port: 8080, public: true }],
  },
  { waitForRunning: true },
);

The spec also accepts lifecycle timers, ssh_keys, volume_ids, gpus, and metadata. See regions, volumes, and networking.

client.sandboxes
create(spec, opts?)Create, returns a Sandbox handle
get(id)Full sandbox state
connect(id)Hydrated handle for an existing sandbox
list({ limit?, offset?, state? }){ rows, total, has_more }
iterate({ state?, pageSize? })Async generator over every page
rename(id, name)Set or clear the label (null clears)
delete(id)Destroy the sandbox

Run commands

const r = await sbx.exec(["uname", "-a"]);   // argv (execve) or a string (sh -c)
r.check();                                    // throws on non-zero exit
console.log(r.exit_code, r.stdout, r.stderr);

await sbx.sh("echo hi && ls /");
await sbx.runCode("print(2+2)", { lang: "python" });   // python | bash | node | sh

exec and runCode default to a 30 second timeout. Raise it with { timeoutMs }. When lang is omitted, runCode uses the sandbox's create-time language. Output is buffered. See exec.

Work with files

Keep write bodies under 16 MiB. For bulk data, attach a volume or fetch it inside the guest with exec.

await sbx.files.write("/app/config.json", JSON.stringify({ debug: true }));
const buf = await sbx.files.read("/app/config.json");   // Uint8Array
console.log(new TextDecoder().decode(buf));

for await (const ev of sbx.files.watch("/app", { maxSeconds: 60 })) {
  if (ev.event === "change") console.log(ev.data.type, ev.data.path);
}
sbx.files
read(path) / write(path, body)Read to Uint8Array; write a string, bytes, or Blob
delete(path, { recurse? })Remove a file or tree
exists(path)Boolean, no throw on a missing path
list(path) / stat(path) / mkdir(path)Entries / metadata / create a directory
find(path, pattern, { max? })Filename glob search
grep(path, pattern, { max? })Recursive content search, file:line:content strings
replace(path, find, replace, { glob? })In-place regex find and replace
watch(path, { intervalMs?, maxSeconds? })Async iterator of ready / change / heartbeat events (ends after maxSeconds, default 300)

See filesystem.

Expose a port

Expose inline at create (URL in the same round-trip) or later:

const { url, token } = await sbx.expose(3000, { ttlSeconds: 3600 });  // private preview URL
await sbx.expose(8080, { public: true });                            // public, no token
console.log(sbx.exposedUrl(8080));
await sbx.unexpose(3000);
sbx networking
expose(port, { public?, ttlSeconds? })Preview URL, private HMAC-token by default; public: true drops the token
unexpose(port)Tear down an exposure
exposedUrl(port)URL of an already-exposed port, or null
revokeExposeToken(token, reason?)Revoke one leaked token; the port stays up
setEgressPolicy({ mode, cidrs })Outbound policy. allow blocks the listed CIDRs (deny-list); deny allows only them (allow-list)
addSshKeys(keys)Replace /root/.ssh/authorized_keys

See networking.

Lifecycle and persistence

await sbx.stop({ waitForStopped: true });   // keeps disk and identity
await sbx.start({ waitForRunning: true });

const twin = await sbx.fork();              // snapshot + boot a running copy
await sbx.snapshot();
await sbx.restore();                        // roll back to the latest snapshot
Lifecycle
refresh()Re-fetch sandbox state
waitRunning(opts?) / waitState(target, opts?)Poll until running, or a target state (a failed/terminal state throws)
stop(opts?) / start(opts?)Stop keeps disk and identity; start boots it again
pause() / resume()Pause and resume
resize({ cpu?, ram_mb?, disk_gb? })Resize a stopped sandbox and cold-boot it (disk_gb grows only)
rename(name) / archive()Relabel, or archive to free resources
delete() / kill()Destroy (kill is an alias)
metrics() / usage({ sinceMs? })Live cpu/mem/disk, and billed usage
Snapshots
snapshot({ scrub_env?, clear_tmp?, clear_ssh_keys? })Point-in-time disk snapshot (scrub flags wipe secrets first)
listSnapshots() / deleteSnapshot(id)Manage this sandbox's snapshots
restore({ snapshotId? })Roll this sandbox back (latest when omitted)
clone({ snapshotId? })Boot a new sandbox from a snapshot; the source keeps running
fork()Snapshot and boot a running copy in one call
backup() / restoreFromBackup()Durable off-host backup and restore

See snapshots.

Templates

Build a template once and reuse it by name, or pass the builder inline to create:

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

const image = Template.fromPythonImage("3.12-slim")
  .pipInstall(["fastapi", "uvicorn"])
  .workdir("/app");

await image.build(client, { name: "my-api" });

// Or boot inline (identical specs cache-hit to one template):
const sbx = await client.sandboxes.create(
  { image, env: { PORT: "8080" } },
  { waitForRunning: true, waitTimeoutMs: 600_000 },
);

Start from fromImage(), or a shortcut for Python, Node, Bun, Ubuntu, Debian, and Alpine. Chain steps to set env vars, the workdir, the user, and the entrypoint. Other steps install packages with pip, npm, or apt, and copy in local files. Finish with build(). Full guide: declarative builder.

Other client methods

Method
client.volumescreate / list / get / delete / attach / detach (feature-gated, see volumes)
client.webhookscreate / list / update / delete / test / deliveries / retryDelivery (see webhooks)
client.regions.list()Regions that can place a sandbox now
client.me()Identity and role of the current bearer
client.health.check(){ status, ts }
client.request(method, path, body?, opts?)Escape hatch for any endpoint, with typed errors and timeouts

Handle errors

Everything the SDK throws extends PlatinumError. It carries .status, .code (the API's machine-readable code), and .body. The SDK does not retry, so handle RateLimitError yourself. Branch on the subclass:

import { NotFoundError, ConflictError, RateLimitError } from "@platinum-dev/sdk";

try {
  await sbx.exec("true");
} catch (e) {
  if (e instanceof NotFoundError)  { /* sandbox gone */ }
  if (e instanceof ConflictError)  { /* inspect e.code, e.g. sandbox_not_running */ }
  if (e instanceof RateLimitError) { /* back off e.retryAfterSeconds */ }
}

Use the generated types

The SDK exports exact request and response shapes from the OpenAPI spec. Use them with client.request():

import type { Schemas, ApiPaths, ApiOperations } from "@platinum-dev/sdk";

type State = Schemas["schemas"]["SandboxState"];

Some endpoints have no typed method: the interactive terminal WebSocket, API keys, audit log, and billing. For those, use client.request() or the API reference. Every method has a Python equivalent: Python SDK.