−25%

on annual Windows plans, until 31 Oct. See plans

EQVPS
Get started

TypeScript sandbox SDK in 5 minutes: npm i @eqvps/sdk to your first run

Install @eqvps/sdk, start a Firecracker sandbox from Node.js, Deno or Bun, run Python, Node and shell code in it, move files and stream a long job. Six steps with every line of code.

This guide gets a sandbox running from JavaScript or TypeScript. You'll start an isolated Firecracker microVM, run code in three languages, move files, and stream output from a long job. Every step is a short snippet.

You need Node.js 18+ (or Deno, or Bun) and an EQVPS account.

1. Get a token

The SDK authenticates with an ordinary account token. Connecting your account shows how to get one from the dashboard, the API or the MCP server.

export EQVPS_API_KEY="your-token"

A new account with no balance gets $1 of sandbox credit the first time it creates a sandbox. That covers this guide many times over.

2. Install the SDK

npm i @eqvps/sdk

The examples use ES modules and top-level await. Save them as .mjs files, or as .ts and run them with npx tsx.

3. Start a sandbox and run code

import { Sandbox } from "@eqvps/sdk";

await Sandbox.with({ tariff: "small" }, async (sb) => {
  console.log(sb.id);
  const r = await sb.run("import platform; print(platform.python_version())");
  console.log(r.exit_code, r.stdout);
});

The output is the sandbox id (sb_ and 24 more characters), then 0 3.12.x. Sandbox.with deletes the sandbox when the callback finishes, also if it throws.

The default language is Python. The other two are one option away:

await Sandbox.with({}, async (sb) => {
  const js = await sb.run("console.log(process.version)", { language: "node" });
  const sh = await sb.run("uname -r && nproc", { language: "bash" });
  console.log(js.stdout, sh.stdout);
});

A failing script doesn't throw. Check r.ok, which is true when exit_code is 0 and the command didn't time out.

4. Shell commands and packages

exec takes a shell string or an argv array. The sandbox has internet access, so npm and pip installs work:

await Sandbox.with({ tariff: "small" }, async (sb) => {
  await sb.exec("mkdir -p /root/app && cd /root/app && npm init -y && npm i lodash", { timeout: 55 });
  const r = await sb.exec(["node", "-e", "console.log(require('/root/app/node_modules/lodash').VERSION)"]);
  console.log(r.stdout);
});

A synchronous call can run for 55 seconds at most. Anything longer goes into a background task (step 6).

5. Files

await Sandbox.with({}, async (sb) => {
  await sb.upload("/root/input.json", JSON.stringify({ values: [3, 5, 8] }));
  await sb.run("import json; d = json.load(open('/root/input.json')); open('/root/out.txt', 'w').write(str(sum(d['values'])))");
  console.log(await sb.downloadText("/root/out.txt"));   // 16
});

upload takes a string or a Uint8Array. download returns a Uint8Array, downloadText a string. One transfer is limited to 5 MB.

6. Long jobs with live output

await Sandbox.with({ tariff: "standard" }, async (sb) => {
  const task = await sb.exec("for i in 1 2 3 4 5; do echo step $i; sleep 20; done", { background: true });
  const res = await task.wait({ onOutput: (out) => process.stdout.write(out) });
  console.log(res.state, res.exit_code);   // done 0
});

The task returns immediately and has no 55-second limit. wait polls every 2 seconds by default (pollIntervalMs changes that). task.kill() stops it; sb.task(id) reattaches from another process.

Errors

import { Sandbox, InsufficientBalanceError, RateLimitError } from "@eqvps/sdk";

try {
  await Sandbox.with({}, async (sb) => console.log((await sb.run("print(1)")).stdout));
} catch (e) {
  if (e instanceof InsufficientBalanceError) console.log("Top up the balance");
  else if (e instanceof RateLimitError) console.log("Busy, retry in", e.retryAfter);
  else throw e;
}

The SDK retries 429 and 503 itself, up to three times, when the server sends Retry-After. The most common 429 comes from running more than two commands at once on one account. Every class is in the SDK reference.

Cost of this guide

Each example lived a few seconds and was billed the 60-second minimum: $0.00055 on small, $0.0011 on standard. The step 6 job ran for about 100 seconds on standard, about $0.002. All of it together is well under a cent. The full price list is in sandbox limits and billing.

Next steps

FAQ

Which runtimes does @eqvps/sdk support?

Node.js 18 and newer, Deno and Bun. It has no dependencies and uses the built-in fetch, so it also runs in a browser, but don't put your account token in front-end code.

Can the sandbox run Python if my app is in TypeScript?

Yes. The language of your app and the language of the sandbox code are separate. run accepts language python, node or bash, and exec runs any shell command.

How do I make sure the sandbox is deleted?

Use Sandbox.with(options, fn), which deletes the sandbox when fn finishes or throws. With TypeScript 5.2 or newer you can also write await using sb = await Sandbox.create().

What does run return when the code fails?

An ExecResult with a non-zero exit_code and the error in stderr. It doesn't throw. Exceptions are only for API errors such as an invalid token or an empty balance.

Comments

No comments yet. Be the first.

Leave a comment

Comments are moderated before they appear.