@boxd-sh/sdk package gives you programmatic access to the full boxd API — create and manage machines, exec commands, stream output, transfer files, manage proxies, templates, disks. It talks to the same public gRPC API as the CLI, so everything you can do from the terminal you can do from TypeScript.
Promise-only, ESM-only. Runs on Node 20+, Bun, and Deno.
Install
Quick start
Authentication
boxd keys create (CLI), the boxd.sh console UI, or directly via the gRPC API. The SDK itself only issues short-lived JWTs (c.token.create()), not long-lived API keys — see Tokens.
Configuration
By default,Compute connects to production. For custom or self-hosted endpoints, override URLs explicitly:
Environment variables
AllCompute options can be supplied via env vars. Constructor args win over env vars.
| Variable | Sets | Default |
|---|---|---|
BOXD_API_KEY | API key (long-lived, recommended) | — |
BOXD_TOKEN | Direct JWT (short-lived) | — |
BOXD_API_URL | gRPC endpoint | http://boxd.sh:9443 |
BOXD_EXCHANGE_URL | Token-exchange URL | https://boxd.sh/api/v1/auth/token |
apiUrl accepts an optional URL scheme that controls TLS:
apiUrl value | Transport |
|---|---|
http://host:port | plaintext (scheme stripped before connecting) |
https://host:port | TLS (scheme stripped before connecting) |
bare host:port | TLS, except localhost / 127.* which stay plaintext |
http://boxd.sh:9443 matches production. Self-hosted clusters can pass apiUrl: "http://my-cluster:9443" to opt into plaintext.
Machine lifecycle
stop/start/reboot (cold) and suspend/resume (warm).
Box fields
Box exposes server-returned fields. Which fields are populated depends on how the Box was obtained:
| Field | create | fork | list | get |
|---|---|---|---|---|
id, name, image, publicIp, status | ✓ | ✓ | ✓ | ✓ |
url, bootTimeMs | ✓ | ✓ | — | — |
forkedFrom | — | ✓ | — | — |
restartPolicy, diskBytes, autoSuspendTimeoutSecs | — | — | — | ✓ |
list / get round-trip, the https://<name>.boxd.sh form is stable, or call box.proxies(). If you need the lifecycle fields off a Box from list / create / fork, re-fetch via c.box.get(box.name).
BoxConfig
create, fork, and template.createVm all take an optional config:
Exec
Streams: stdout vs stderr
proc.stderr / r.stderr is populated for non-PTY execs. The subprocess’s
fd 2 is delivered separately from fd 1 — useful when a tool’s progress lives
on stderr while the answer is on stdout (e.g. codex exec, cargo build).
Under pty: true / interactive: true, the kernel TTY layer merges stderr
into stdout (that’s how terminals work), so everything arrives on proc.stdout
and proc.stderr stays empty. Set pty: false if you need the split.
closeStdin
Set closeStdin: true (only valid with stream: true on a non-PTY exec) to
have the SDK close the client send half of the bidi stream right after the
command starts. Headless one-shots that read stdin (claude -p, jq, cat file) see EOF immediately and proceed; without it they hang for several
seconds (or forever) waiting on stdin. Passing it together with pty: true
or interactive: true throws a clear error rather than silently dropping the
flag — a PTY shell needs stdin open for user input.
PTY size + resize
For PTY/interactive execs, passcols and rows to set the initial terminal
geometry, and call proc.resize(cols, rows) to update it mid-session when the
local terminal changes size. This is what makes TUI apps like claude, vim,
htop render at the right width.
cols and rows fall back to the server default of 80×24.
resize() on a non-PTY exec is a harmless no-op.
File transfer
/home/boxd.
Proxies
Every box is live athttps://name.boxd.sh forwarding to port 8000 by default. Additional subdomain proxies forward to other ports.
Logs
Templates
Reusable image +BoxConfig frozen together.
Disks
Independent persistent volumes you can attach and detach from boxes.Domains
Custom HTTPS domains bound to a box. DNS must already point at the boxd proxy — see HTTPS.Networks
Custom L2 networks for VM-to-VM communication.Tokens
Issue scoped JWTs for delegated access. The raw token string is only returned at creation — store it then.Identity
VERSION constant matching package.json:
Errors
BoxdError. Subclasses:
| Class | gRPC status |
|---|---|
AuthenticationError | UNAUTHENTICATED, PERMISSION_DENIED |
NotFoundError | NOT_FOUND |
QuotaExceededError | RESOURCE_EXHAUSTED |
InvalidArgumentError | INVALID_ARGUMENT, ALREADY_EXISTS |
TimeoutError | DEADLINE_EXCEEDED |
ConnectionError | UNAVAILABLE |
InternalError | INTERNAL, UNKNOWN |
grpcCode (numeric gRPC status — see grpc.StatusCode) for finer-grained handling:
Update notifications
Every gRPC response carries anx-boxd-ts-sdk-latest header set by the boxd proxy. The SDK’s interceptor compares it to the installed version and prints a one-time stderr line via console.warn if a newer release is available:
-dev.N suffixes).
Async disposal
Node 20+, Bun, and Deno support TC39 explicit-resource-management.Compute implements Symbol.asyncDispose, so await using cleans up the gRPC channel automatically:
await using, call await c.close() when you’re done.
Reference
CLI
Same API, accessed from the terminal. Useful for one-offs and shell scripting.
Python SDK
Same API, sync and async, for Python codebases.