---
title: Superserve
description: Run Firecracker microVMs on Superserve. Pause and resume, snapshots, egress allow lists, secret bindings, and HTTPS previews.
---

`superserve()` runs each sandbox as a Firecracker microVM on [Superserve](https://docs.superserve.ai/sandbox/create). It calls Superserve's HTTP API directly, so there is no second package to install.

## Install

**npm**

```bash
npm install @opencoredev/sandbox-sdk
```

**pnpm**

```bash
pnpm add @opencoredev/sandbox-sdk
```

**yarn**

```bash
yarn add @opencoredev/sandbox-sdk
```

**bun**

```bash
bun add @opencoredev/sandbox-sdk
```

Set `SUPERSERVE_API_KEY` on your server, and keep it out of browser bundles. Commands that take stdin use the runtime's global `WebSocket`, which Node 22 and Bun have.

## Quickstart

```ts
import { createSandbox } from "@opencoredev/sandbox-sdk";
import { superserve } from "@opencoredev/sandbox-sdk/superserve";

await using box = await createSandbox(superserve({ installPython: true }));
const result = await box.run("node --version");
console.log(result.stdout);
```

The default template, `superserve/node-22`, has no Python, which the adapter needs. `installPython: true` installs it on first use. `await using` deletes the VM when the block ends.

## What it supports

| Feature                        | Support     | Notes                                                       |
| ------------------------------ | ----------- | ----------------------------------------------------------- |
| Commands and streaming         | Partial     | Output arrives when the command finishes                    |
| Files                          | Yes         | `mode` uses a `chmod` after the write                       |
| Background processes           | Yes         |                                                             |
| Preview URLs                   | Yes         | Private by default; send the returned header                |
| Pause and resume               | Yes         | VM state survives                                           |
| Snapshots                      | Yes         | Kept until deleted; no `expiresInMs`                        |
| Machine size                   | No          | Set by the template                                         |
| Network policy                 | Yes         | `"none"` and allow lists of hosts, wildcards, or IPv4 CIDRs |
| Hard lifetime (`timeoutMs`)    | No          | Use `autoPauseMs` and `autoDeleteMs`                        |
| Idle timeout (`idleTimeoutMs`) | No          |                                                             |
| Keys                           | Best-effort | Found by metadata lookup                                    |
| Terminal                       | No          |                                                             |

The [capability matrix](/docs/reference/capabilities) compares these with every other provider.

## Run commands

Await `box.run` for the result. Iterating works too, but output is read from guest disk after the command exits, so chunks arrive together at the end.

```ts
import { createSandbox } from "@opencoredev/sandbox-sdk";
import { superserve } from "@opencoredev/sandbox-sdk/superserve";

await using box = await createSandbox(superserve({ installPython: true }));
const result = await box.run("ls -la", { cwd: "/workspace", timeoutMs: 30_000 });
console.log(result.exitCode, result.stdout);

for await (const chunk of box.run("echo one; echo two")) {
  process.stdout.write(chunk.text);
}
```

## Files

Relative paths resolve against `/workspace`.

```ts
import { createSandbox } from "@opencoredev/sandbox-sdk";
import { superserve } from "@opencoredev/sandbox-sdk/superserve";

await using box = await createSandbox(superserve({ installPython: true }));
await box.files.write("notes/todo.txt", "ship the docs\n");
await box.files.write(".env", "TOKEN=abc\n", { mode: 0o600 });
console.log(await box.files.read("notes/todo.txt"));
console.log(await box.files.list("notes"));
```

Superserve's file API has no mode, so the SDK runs `chmod` right after a write that passes `mode`.

## Background processes and previews

No ports to declare. `box.preview(port)` publishes the port as private and returns the auth header in `preview.headers`. Send it with every request. With `previewAccess: "public"`, ports are published publicly and `preview.headers` is absent. Ports must be between 1024 and 65535, except 49983.

```ts
import { createSandbox } from "@opencoredev/sandbox-sdk";
import { superserve } from "@opencoredev/sandbox-sdk/superserve";

await using box = await createSandbox(superserve({ installPython: true }));
await box.start("python3 -m http.server 3000", { name: "web", ready: { port: 3000 } });
const preview = await box.preview(3000, { waitMs: 10_000 });
const response = await fetch(preview.url, { headers: preview.headers });
console.log(response.status);
```

## Pause, resume, and snapshots

Pause and resume keep the VM state. `snapshot()` returns an ID that `from` accepts.

```ts
import { createSandbox } from "@opencoredev/sandbox-sdk";
import { superserve } from "@opencoredev/sandbox-sdk/superserve";

await using box = await createSandbox(superserve({ installPython: true }), { key: "user:123" });
await box.pause();
await box.resume();

const { id } = await box.snapshot();
await using copy = await createSandbox(superserve(), { from: id });
```

Snapshots outlive their source and have no expiry. Delete one through the API with `provider.api.request("DELETE", "/snapshots/{snapshotId}", { params: { snapshotId: id } })`. `snapshot({ expiresInMs })` with a value above 0 fails with `unsupported`.

## Size, network, and lifetime

Resources come from the template. `size`, `idleTimeoutMs`, and `timeoutMs` fail with `unsupported` before a VM is allocated.

```ts
import { createSandbox, SandboxError } from "@opencoredev/sandbox-sdk";
import { superserve } from "@opencoredev/sandbox-sdk/superserve";

try {
  await createSandbox(superserve(), { size: "large" });
} catch (error) {
  if (error instanceof SandboxError && error.code === "unsupported") {
    console.log("Pick a template with the machine you need.");
  }
}
```

`network` maps to Superserve egress rules. `"none"` denies `0.0.0.0/0`. An allow list denies everything else and accepts hostnames, `*.example.com` wildcards (subdomains only, not the apex), IPv4 addresses, and IPv4 CIDRs. When the list holds a hostname, the adapter also allows Superserve's DNS resolvers, `1.1.1.1/32` and `8.8.8.8/32`, because names can't resolve otherwise. The adapter reads the rules back after creation and fails with `unsupported` if they differ.

```ts
import { createSandbox } from "@opencoredev/sandbox-sdk";
import { superserve } from "@opencoredev/sandbox-sdk/superserve";

await using box = await createSandbox(superserve({ installPython: true }), {
  network: { allow: ["registry.npmjs.org", "*.github.com"] },
});
```

Superserve has no hard lifetime or idle timeout. It has an auto-pause window and an auto-delete window, set on the provider:

```ts
import { createSandbox } from "@opencoredev/sandbox-sdk";
import { superserve } from "@opencoredev/sandbox-sdk/superserve";

const provider = superserve({
  installPython: true,
  autoPauseMs: 30 * 60_000, // pause after 30 minutes of active time per session
  autoDeleteMs: 24 * 60 * 60_000, // delete after a day spent paused
});
await using box = await createSandbox(provider);
```

`autoPauseMs` counts active time per session and re-arms on resume. `autoDeleteMs` counts from the pause, and `0` deletes the VM as soon as it pauses. Both round up to whole seconds.

## Configuration

| Option             | Type                     | Default                     | Env var               |
| ------------------ | ------------------------ | --------------------------- | --------------------- |
| `apiKey`           | `string`                 |                             | `SUPERSERVE_API_KEY`  |
| `baseUrl`          | `string`                 | `https://api.superserve.ai` | `SUPERSERVE_BASE_URL` |
| `template`         | `string`                 | `"superserve/node-22"`      |                       |
| `installPython`    | `boolean`                | `false`                     |                       |
| `requestTimeoutMs` | `number`                 | `90_000`                    |                       |
| `autoPauseMs`      | `number`                 | Never                       |                       |
| `autoDeleteMs`     | `number`                 | Never                       |                       |
| `secretBindings`   | `Record<string, string>` | None                        |                       |
| `previewAccess`    | `"private" \| "public"`  | `"private"`                 |                       |

`secretBindings` maps env var names to [Superserve secrets](https://docs.superserve.ai/secrets/overview) you created: `{ OPENAI_API_KEY: "openai-prod" }`. The sandbox sees a proxy token in the env var, and Superserve attaches the real credential to outbound requests for the secret's hosts. These are secret names, not values, unlike `createSandbox`'s `secrets` option.

A template needs Git, tar, and `nohup`, plus Python 3 unless `installPython` is on. Installing Python needs root and package-repository access and slows the first operation. For fast starts, build a template with Python 3 included; the repo's benchmark has one in `bench/templates/superserve/build.ts`.

## Call the API directly

`box.native` calls any Superserve endpoint with the adapter's credentials, retries, and error mapping. `{sandboxId}` in a path fills in from the sandbox. Superserve has two planes:

- `"api"`, the default, is the control plane at `https://api.superserve.ai`, sent with your API key.
- `"data"` is exec and files inside the sandbox. The adapter sends the sandbox's access token and routing hint, and activates the sandbox again when they expire or the data plane refuses them.

`request` returns the parsed JSON body. `fetch` returns the raw `Response` for bytes and streams. A non-2xx answer rejects with `SandboxError` from both.

```ts
import { createSandbox } from "@opencoredev/sandbox-sdk";
import { superserve } from "@opencoredev/sandbox-sdk/superserve";

await using box = await createSandbox(superserve({ installPython: true }));
const ports = await box.native.request<{ ports: { port: number }[] }>(
  "GET",
  "/sandboxes/{sandboxId}/preview-ports",
);
console.log(ports.ports);

await box.files.write("report.txt", "done\n");
const file = await box.native.fetch("GET", "/files", {
  plane: "data",
  query: { path: "/workspace/report.txt" },
});
console.log(await file.text());
```

`provider.api` is the same API with no sandbox bound, for account-level calls on the control plane. The `data` plane needs a sandbox, so it works only on `box.native`.

```ts
import { superserve } from "@opencoredev/sandbox-sdk/superserve";

const provider = superserve();
const sandboxes = await provider.api.request<{ id: string; status: string }[]>("GET", "/sandboxes");
console.log(sandboxes.length);
```

To use the official SDK, install `@superserve/sdk` yourself and pass its import to `sdk`. On a sandbox you get the SDK's `Sandbox` from `Sandbox.connect`, which resumes the sandbox if it is paused. On `provider.api` you get the `Sandbox` class.

```ts check="skip"
import { createSandbox } from "@opencoredev/sandbox-sdk";
import { superserve } from "@opencoredev/sandbox-sdk/superserve";

await using box = await createSandbox(superserve({ installPython: true }));
const sandbox = await box.native.sdk(() => import("@superserve/sdk"));
console.log(await sandbox.getInfo());
```

If `@superserve/sdk` isn't installed, `sdk` rejects with `unavailable`.

## Limitations

- Keys are best-effort. Pass a [`locator`](/docs/concepts/sandboxes-and-keys#exact-and-best-effort-keys) for exact get-or-create.
- No sizes, idle timeouts, hard lifetimes, or terminals.
- Egress rules cover IPv4 only.
- Command output isn't live.
- Connecting to a paused VM resumes it.

## How it works

- Commands run under a guest supervisor in their own process group. A timeout or abort triggers a separate cancellation check, and an unconfirmed kill returns `outcome_unknown`.
- A restore inherits the snapshot's auto-pause window. The adapter replaces it with `autoPauseMs`, or clears it when `autoPauseMs` is unset.
- Listing uses native metadata and doesn't wake VMs.
- A failed snapshot reports `outcome_unknown` with the snapshot name. List the source's snapshots before retrying.

## Next steps

- [Processes and previews](/docs/concepts/processes-and-previews)
- [Lifecycle and cleanup](/docs/concepts/lifecycle-and-cleanup)
