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/sdkRequires 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 | shexec 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.volumes | create / list / get / delete / attach / detach (feature-gated, see volumes) |
client.webhooks | create / 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 */ }
}| Error | Raised when |
|---|---|
ValidationError | The request was rejected as invalid |
AuthenticationError | The key is missing, invalid, or revoked |
ForbiddenError | The key lacks the scope for that call |
NotFoundError | The sandbox, template, or volume does not exist |
ConflictError | The resource is in the wrong state for the call |
RateLimitError | Too many requests, back off with .retryAfterSeconds |
ServerError | The API failed |
PlatinumTimeoutError | A client deadline or a wait helper expired |
PlatinumConnectionError | The connection failed, with the original error on .cause |
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.