−25%

on annual Windows plans, until 31 Oct. See plans

EQVPS
Get started

Sandboxes

Isolated Linux microVMs for code that your scripts and AI agents run. One starts in about a second, runs Python, Node.js or bash, and is billed per second from the same balance as your servers.

What a sandbox is

A small Linux machine you create through the API or MCP, use for a few seconds or a few days, and delete. Outbound internet works (pip install, npm install, git clone). There are no inbound ports and no SSH: commands and files go through the API. Inside: Python 3.12 with pip, Node.js 22 with npm, bash, git and curl.

Two modes

Ephemeral (default) — for one task. It is deleted when you call delete, after the idle timeout (5 minutes by default, up to 1 hour) or when its TTL ends (up to 24 hours). Billed per second, minimum 60 seconds.
Persistent — keeps its disk and state for up to 30 days. Billed per started hour, and a calendar month never costs more than 730 hours minus 25%. If the balance runs out, it is paused to a snapshot (not billed) and resumes after a top-up; paused snapshots are kept for 14 days.

Tariffs

TariffvCPURAMDiskNetworkPer hourPersistent, month max
micro0.25512 MB3 GB50 Mbit/s$0.0165$9.03
small0.51 GB5 GB100 Mbit/s$0.033$18.07
standard12 GB10 GB200 Mbit/s$0.066$36.14
plus24 GB15 GB300 Mbit/s$0.132$72.27
pro48 GB20 GB400 Mbit/s$0.264$144.54
max816 GB40 GB500 Mbit/s$0.528$289.08

You pay for the vCPU and RAM of the tariff you pick: $0.040 per vCPU-hour and $0.013 per GiB-hour. Live prices, no token needed: GET /sandboxes/tariffs.

Limits worth knowing

One command runs for up to 55 seconds; longer jobs run as background tasks that you poll. Files go up to 5 MB per request, bigger ones (up to 2 GB) through one-time download and upload links. An account can run up to 20 sandboxes and 2 commands at the same time.

Environment variables

Pass secrets and settings as env. Set on create, they apply to every command in the sandbox; set on exec or run, they apply to that call and override the sandbox values. Up to 64 variables, 32 KB per value and 64 KB in total. Values are stored encrypted, are never returned by the API and are not written to logs: responses show only the names (env_keys).

API=https://api.eqvps.com/api/v1/eqvps
AUTH="Authorization: Bearer $EQVPS_API_KEY"

# env for the whole sandbox (only the names come back, as env_keys)
SB=$(curl -s -X POST $API/sandboxes -H "$AUTH" -H "Content-Type: application/json" \
  -d '{"tariff":"small","env":{"DB_URL":"postgres://user:pass@db/app","MODE":"test"}}' | jq -r .id)

# env for one call, merged over the sandbox env
curl -s -X POST $API/sandboxes/$SB/exec -H "$AUTH" -H "Content-Type: application/json" \
  -d '{"command":"echo $MODE $RUN_ID","env":{"RUN_ID":"42"}}'

Spending limit

By default sandboxes have no spending limit. You can set a daily and a monthly cap in the dashboard or with PUT /sandboxes/budget. When a cap is reached, new sandboxes get 429 budget_exceeded, running ephemeral sandboxes are deleted and persistent ones are paused with their disk kept. They resume when you raise the cap or a new day or month starts (UTC).

# daily cap $5, monthly cap $50 (null = no cap)
curl -s -X PUT $API/sandboxes/budget -H "$AUTH" -H "Content-Type: application/json" \
  -d '{"daily_usd":5,"monthly_usd":50}'

# spent today / this month and when the caps reset (UTC)
curl -s $API/sandboxes/budget -H "$AUTH"

Quick start: MCP (Claude Code, Cursor and other clients)

Connect the MCP server once, then ask the agent in plain words. Sandbox tools: create_sandbox, run_code, exec_command, get_task, kill_task, get_upload_url, get_download_url, list_sandboxes, get_sandbox, kill_sandbox, sandbox_pricing.

claude mcp add --transport http eqvps https://mcp.eqvps.com/mcp \
  --header "Authorization: Bearer $EQVPS_API_KEY"
Create a small sandbox, run print(2 + 2) in Python, show me the output and delete the sandbox.

Quick start: REST from Python and Node.js

No extra packages are needed. Put your token into the EQVPS_API_KEY environment variable (how to get one is described in “Connecting your account”).

Python
# first_sandbox.py — Python 3.8+, standard library only
import json, os, urllib.request

API = "https://api.eqvps.com/api/v1/eqvps"
KEY = os.environ["EQVPS_API_KEY"]

def call(method, path, body=None):
    req = urllib.request.Request(API + path, method=method,
        data=json.dumps(body).encode() if body is not None else None,
        headers={"Authorization": "Bearer " + KEY,
                 "Content-Type": "application/json", "Accept": "application/json"})
    with urllib.request.urlopen(req, timeout=90) as r:   # HTTPError on 401/402/…
        return json.loads(r.read() or b"{}")

sb = call("POST", "/sandboxes", {"tariff": "small"})
try:
    r = call("POST", f"/sandboxes/{sb['id']}/run",
             {"language": "python", "code": "print(2 + 2)"})
    print(r["exit_code"], r["stdout"])                    # 0 4
finally:
    call("DELETE", f"/sandboxes/{sb['id']}")
Node.js
// first_sandbox.mjs — Node.js 18+ (built-in fetch), run: node first_sandbox.mjs
const API = "https://api.eqvps.com/api/v1/eqvps";
const KEY = process.env.EQVPS_API_KEY;

async function call(method, path, body) {
  const r = await fetch(API + path, {
    method,
    headers: { Authorization: `Bearer ${KEY}`, "Content-Type": "application/json", Accept: "application/json" },
    body: body ? JSON.stringify(body) : undefined,
  });
  const data = await r.json();
  if (!r.ok) throw new Error(`${r.status} ${data.error ?? ""} ${data.message ?? ""}`);
  return data;
}

const sb = await call("POST", "/sandboxes", { tariff: "small" });
try {
  const r = await call("POST", `/sandboxes/${sb.id}/run`, { language: "node", code: "console.log(2 + 2)" });
  console.log(r.exit_code, r.stdout); // 0 4
} finally {
  await call("DELETE", `/sandboxes/${sb.id}`);
}

SDK for Python and TypeScript

Prefer a library? The Python and TypeScript packages wrap the same REST calls, retry 429 and 503 on their own and always delete the sandbox when the block ends. Install and use:

Python · eqvps
# pip install -U eqvps       https://pypi.org/project/eqvps/  (0.2.0+ for env)
# "eqvps sandbox create" = Sandbox.create() here · create_sandbox in MCP · POST /sandboxes in REST (no CLI)
from eqvps import Sandbox

# EQVPS_API_KEY from the environment; env = secrets for your code, stored encrypted
with Sandbox.create(tariff="small", env={"DB_URL": "postgres://user:pass@db/app"}) as sb:
    r = sb.run("print(2 + 2)")
    print(r.stdout, r.exit_code)             # "4\n" 0
    sb.upload("/root/data.csv", "a,b\n1,2\n")
    print(sb.exec("wc -l /root/data.csv").stdout)
    print(sb.exec("echo $RUN_ID", env={"RUN_ID": "42"}).stdout)   # env for one call
# the sandbox is deleted when the block exits, also on errors
TypeScript · @eqvps/sdk
// npm i @eqvps/sdk@latest   https://www.npmjs.com/package/@eqvps/sdk  (0.2.0+ for env)
import { Sandbox } from "@eqvps/sdk";

// EQVPS_API_KEY from the environment; env = secrets for your code, stored encrypted
await Sandbox.with({ tariff: "small", env: { DB_URL: "postgres://user:pass@db/app" } }, async (sb) => {
  const r = await sb.run("print(2 + 2)");
  console.log(r.stdout, r.exit_code);                    // "4\n" 0
  console.log((await sb.exec("echo $RUN_ID", { env: { RUN_ID: "42" } })).stdout); // env for one call
});                                                       // deleted afterwards, also on errors

Connecting your account: token, config, first sandbox, balance →

Comments

No comments yet. Be the first.

Leave a comment

Comments are moderated before they appear.