npx skills add ...
npx skills add upstash/skills --skill upstash-box-js
Work with the @upstash/box TypeScript/JavaScript SDK for sandboxed cloud containers with AI agents, shell, filesystem, git, cron schedules, snapshots, and a headless browser. Use when building with Upstash Box, creating a sandbox or isolated environment to run untrusted or agent-generated code, running AI coding agents in containers, giving an agent a cloud dev environment with a shell and repository, browser automation from a box, scheduling recurring jobs inside a box, saving and restoring snapshots, or orchestrating parallel boxes.
npx skills add upstash/skills --skill upstash-box-js
Sandboxed cloud containers with built-in AI agents, shell, filesystem, git, cron schedules, and an optional headless browser.
The Python SDK (upstash-box) mirrors this API with snake_case names — see the
upstash-box-py skill for the Python spelling of everything below.
Set UPSTASH_BOX_API_KEY env var or pass apiKey to constructors.
Anonymous telemetry headers are sent by default. Opt out with the
UPSTASH_DISABLE_TELEMETRY env var, or enableTelemetry: false in the config
(the only option on runtimes without process.env, e.g. Cloudflare Workers).
Injected into every box you create.
options is forwarded to the harness — the accepted keys depend on which one
the box runs. Typing the box (Box.create<Agent.ClaudeCode>({...})) narrows
options to that harness's shape.
Codex keys are converted to the backend's snake_case for you — always write them camelCase.
harness is required (provider / runner are deprecated aliases). Model
enums: ClaudeCode, OpenAICodex, OpenCodeModel, CursorModel,
OpenRouterModel, VercelModel — or any plain provider-prefixed string.
Run your own agent binary inside the box instead of a managed harness.
Every run (agent, command, or code) returns a Run<T>:
exec.session() opens a WebSocket to a process that is still running — stdin,
streamed stdout/stderr, PTY resize, and signals. Node-only: auth is a handshake
header, which browsers cannot set (ws ships as an SDK dependency, nothing to
install). Available on Box and EphemeralBox.
The session owns the process: close(), a dropped connection, or your process
exiting all kill the command, and a session cannot be reattached. Use wait()
to run something to completion.
The SDK tracks cwd client-side. All operations (exec, files, git, agent) run relative to it.
Clones land inside the box's isolated container, never on the caller's machine. Cloned
code is data until something runs it — treat an untrusted repo as untrusted input, and
pair it with a restrictive networkPolicy (see below) before running its build or tests.
Every git call except clone runs in the box's current directory, so cd into the
clone first. At the workspace root there is no repository, and status comes back
empty, which reads as a clean tree.
Cron tasks on a box — shell commands or agent prompts. Available on Box and EphemeralBox. Cron is UTC.
Create the box with browser: true to drive a headless Chromium. Tab management
lives on box.browser; every page operation lives on the Tab handle.
extract / observe / act(instruction) are AI-powered and metered;
act(action) replays an already-resolved action with no LLM call and no tokens.
tab.run() — the autonomous multi-step browser agent — was removed in 0.7.0,
along with the BrowserRunOptions / BrowserRunResult / BrowserRunStep types
(Stagehand v4 dropped the underlying agent primitive). The DOM-aware browser now
exposes observe, act, and extract only. Three replacements:
1. Drive your own loop — resolve steps once with observe, then replay them
with act(action) so the model stays out of the hot path; extract is the stop check.
2. Hand the goal to the in-box agent — browser: true auto-wires the
chrome-devtools MCP (Chromium already warmed on 127.0.0.1:9222) into the box's
coding agent, so box.agent.run({ prompt }) drives the browser itself and iterates
until done. No tab.create() needed first. This bills coding-agent model tokens
rather than browser-AI metering, and needs an agent harness + key.
3. Connect over CDP with Playwright / Puppeteer via box.browser.cdpUrl() when
the flow is fully deterministic.
Lightweight, short-lived boxes (max 3 days). Supports exec, files, schedule, cd, network policy, and snapshots. No agent, git, skills, labels namespace, browser, or public URLs.
Expose box ports as public URLs with optional auth.
Install agent skills from the Context7 registry. Format: owner/repo/skill-name.
An installed skill becomes instructions for the box's agent, so pin skills to owners you trust the same way you would a dependency. Skills resolve from the registry at box creation, not from arbitrary URLs, and they only ever run inside the box's container.
Attach MCP servers to the box agent. An attached server supplies tools the agent can call,
so use servers you control or trust — and keep networkPolicy restrictive when the agent
also handles untrusted input.
Shell into a box directly (Box API key is the SSH password):
/workspace/home, not /home or /box.cd() is client-side tracking — it validates the path exists but doesn't change the box's shell cwd. All SDK methods use it automatically.agent.harness is required; provider / runner still work but are deprecatedbox.fork() — it was removed from the SDK. Snapshot the box and use Box.fromSnapshot() instead.EphemeralBox does NOT support agent, git, skills, browser, or public URLs — use full Box for those (it does support schedule and snapshots)run.exitCode is null for agent runs, only available for exec commandsrun.result is stdout on success and stderr on failure — a command that exits 0 writing only to stderr yields ""; read run.stderr for itfiles.download({ folder }) takes a path inside the box; output lands in ./<basename> locallyfiles.read() slices only when length is present — { offset } alone reads the whole file, and { length: 0 } reads nothingfiles.stat() is an lstat by default: a symlink reports type: "symlink" unless you pass { follow: true }files.remove() needs { recursive: true } for a directory, and files.mkdir() needs { parents: true } for nested pathsexec.session() is Node-only (the WebSocket handshake carries an auth header) and the handle owns the process — close() or a dropped connection kills the command, and sessions cannot be reattachedexec.session({ tty: true }) merges stderr into stdout, so onStderr never fires for a PTY sessionbox.browser requires a box created with browser: truetab.run() — the autonomous browser agent was removed in 0.7.0. Loop observe + act(action) + extract yourself, hand the goal to the in-box agent, or drive Playwright over cdpUrl()tab.act(action) (replaying an observe() result) costs no tokens and needs no model provider key; only act(instruction) with a string is meteredgetInitCommand / setInitCommand / deleteInitCommand throw unless the box was created with keepAlive: truebox.delete() is irreversible — snapshot first if you need the stategit.token in BoxConfig for private repos and PRsBox.fromSnapshot() creates a new box — it does not modify the original, and it does not forward browser, skills, or mcpServers from the config you passEphemeralBox has no updateNetworkPolicy — set networkPolicy at create timeresponseSchema and browser schema need zod installed (peer dependency, v3 or v4)timeout values are milliseconds