−25%

on annual Windows plans, until 31 Oct. See plans

EQVPS
Get started

Sandbox API reference: Python and TypeScript SDK

Every method of the EQVPS sandbox SDK in one page: parameters, defaults, return values and all 11 error classes, with the same example in Python and TypeScript.

Last verified: 2026-10-10 · SDK 0.2.0 (PyPI eqvps, npm @eqvps/sdk)

The SDK is a thin wrapper over the sandbox REST API. It saves you three things: building requests by hand, retrying when the platform is busy, and forgetting to delete a sandbox when your code throws. Everything here matches version 0.2.0 of both packages.

If you have never created a sandbox, the 5-minute connection guide gets you a token first. What a sandbox is and what's inside it is covered in Sandboxes.

Install and configure

pip install eqvps          # Python 3.8+
npm i @eqvps/sdk           # Node.js 18+, Deno, Bun
export EQVPS_API_KEY=...   # your EQVPS account token

The same program in both languages: create a sandbox, run code, delete it.

from eqvps import Sandbox

with Sandbox.create(tariff="small") as sb:
    r = sb.run("print(2 + 2)")
    print(r.exit_code, r.stdout)   # 0 4
import { Sandbox } from "@eqvps/sdk";

await Sandbox.with({ tariff: "small" }, async (sb) => {
  const r = await sb.run("print(2 + 2)");
  console.log(r.exit_code, r.stdout); // 0 4
});

Creating and finding sandboxes

Sandbox.create(...) starts a sandbox, usually in about a second. Python takes keyword arguments, TypeScript one options object.

PythonTypeScriptDefaultMeaning
modemode"ephemeral""ephemeral" (per second) or "persistent" (per started hour, keeps its disk)
tarifftariff"small"micro, small, standard, plus, pro, max
idle_timeoutidleTimeout300seconds without activity before an ephemeral sandbox is deleted, up to 3600
ttlttl—maximum lifetime in seconds: up to 86400 ephemeral, 2592000 persistent
envenv—environment variables for every command, stored encrypted
api_key, base_urlapiKey, baseUrlenv varsoverride EQVPS_API_KEY / EQVPS_API_URL
timeouttimeoutMs70 sHTTP timeout per request
max_retriesmaxRetries3retries on 429/503

Sandbox.connect(id) attaches to an existing sandbox, typically a persistent one you created yesterday. Sandbox.list() returns every sandbox on the account, running and paused.

Properties: id (sb_ + 24 hex characters), mode, tariff, state (running, starting, paused, pausing, resuming, deleting, deleted), env_keys / envKeys (names only, values never come back) and info (the raw object). refresh() reloads them.

kill() deletes the sandbox and stops billing. Calling it on a sandbox that is already gone is not an error.

Running code

run(code, language="python", timeout=30) sends code on stdin to Python 3.12, Node.js 22 or bash ("python", "node", "bash"). exec(command, cwd=None, stdin=None, timeout=30) runs a shell command: a string goes through bash -lc, a list is executed as argv without a shell.

Both return an ExecResult:

FieldMeaning
exit_codeprocess exit code
stdout, stderroutput, up to 1 MiB per stream
timed_outthe command hit its timeout
truncatedoutput was cut
duration_mshow long it ran
okexit_code == 0 and not timed out

A synchronous call can run for 55 seconds at most. Both methods also take env for that one call; it overrides the values set at create.

Long jobs: background tasks

Pass background=True (TypeScript: { background: true }) and you get a Task straight away. It has no 55-second limit and can run until the sandbox's lifetime ends. Up to 8 tasks per sandbox.

task = sb.exec("cd /root/app && python3 -m pytest -q", background=True)
result = task.wait(on_output=lambda out, err: print(out, end=""))
print(result.state, result.exit_code)   # done 0
const task = await sb.exec("cd /root/app && npm test", { background: true });
const result = await task.wait({ onOutput: (out) => process.stdout.write(out) });
MethodWhat it does
task.logs()new stdout and stderr since the previous call
task.status()state without consuming output: running, done, failed, killed, timeout
task.wait(timeout, poll_interval=2, on_output)polls until the task ends, returns TaskResult
task.kill()stops the task and its processes
sb.task(id) / sb.tasks()reattach to a task / list tasks

If wait hits its own timeout it raises SandboxTimeoutError, but the task keeps running. A running task also keeps an ephemeral sandbox from being deleted for inactivity.

Files and usage

upload(path, content, mode=None) writes a file at an absolute path and returns its size. download(path) returns bytes, download_text(path) / downloadText(path) a string. One transfer is limited to 5 MB.

usage() returns running seconds, CPU seconds used, outbound bytes, the hourly price, billed_usd and estimated_total_usd. tariffs() is a plain function that needs no key and returns current prices and limits. Billing rules are on Sandbox limits and billing.

Errors

Every API error is a subclass of EqvpsError with status (HTTP), code (stable string) and body. retry_after / retryAfter is set when the server sends it.

ClassHTTPTypical codeWhat to do
AuthenticationError401unauthenticatedcheck the token
InsufficientBalanceError402insufficient_balancetop up the balance
NotFoundError404not_foundwrong id, or a file that doesn't exist
SandboxPausedError409sandbox_pausedpaused after the balance ran out; resumes after a top-up
SandboxDeletedError410sandbox_deletedgone for good
FileTooLargeError413file_too_largekeep transfers under 5 MB
ValidationError422invalid_requestfix the parameters
RateLimitError429too_many_concurrent, too_many_tasks, rate_limitedwait and retry
BudgetExceededError429budget_exceededyour daily or monthly sandbox limit was hit
CapacityError503capacitytry again shortly or pick a smaller tariff
SandboxTimeoutError—request_timeout, wait_timeoutthe HTTP request (or wait) timed out, not the command

Two limits cause most 429s: an account runs 2 commands at the same time and holds up to 20 sandboxes. The SDK retries these when the server says how long to wait; after three attempts you get the exception.

When to skip the SDK

Any language with an HTTP client can call the API directly; the endpoints are in the OpenAPI file at https://eqvps.com/openapi.json. Agents in Claude, Cursor or other MCP clients don't need code at all: the MCP server has the same sandbox tools. And if you just want to see it work, the sandbox page lists the tariffs and the $1 trial for new accounts.

FAQ

Which versions of Python and Node.js does the SDK support?

The Python package needs Python 3.8 or newer and has no dependencies. The TypeScript package has no dependencies either and runs on Node.js 18+, Deno, Bun and in browsers that have fetch.

Where does the SDK get my API key?

From the api_key / apiKey argument, or from the EQVPS_API_KEY environment variable. The key is an ordinary EQVPS account token. EQVPS_API_URL overrides the API address; you only need it for testing.

Does the SDK retry failed requests?

It retries 429 and 503 up to 3 times when the server sends Retry-After, waiting at most 30 seconds each time. Those statuses mean the request was refused before anything ran, so retrying run and exec is safe. Network errors are retried only for GET requests; run, exec and upload are never sent twice.

Is a non-zero exit code an exception?

No. run and exec return a result with exit_code, stdout and stderr. Check result.ok or result.exit_code. Exceptions are raised only for API errors such as an empty balance or an invalid token.

How do I make sure a sandbox is deleted if my code crashes?

Use the scoped form: with Sandbox.create() as sb in Python, Sandbox.with(options, fn) or await using in TypeScript. The sandbox is deleted when the block ends, also after an exception.

Comments

No comments yet. Be the first.

Leave a comment

Comments are moderated before they appear.