Python and JavaScript/TypeScript SDKs

Dependency-free Python and JavaScript/TypeScript clients covering sandboxes, execution, files, preview URLs and account operations — snapshots are available over REST only for now. The Python client installs from the Python Package Index with a single pip install; the JavaScript client from npm with a single npm install.

Install

The client is published on PyPI: pypi.org/project/sandbox-as-a-service. It uses only the Python standard library, so installing it pulls in no dependencies. It supports Python 3.8 and newer.

pip install sandbox-as-a-service

Alternative / offline: the site also serves the same release as a standard PEP 517 source distribution, so a pinned URL install works without PyPI:

pip install https://sandbox-as-a-service.com/sdk/sandbox-as-a-service-python.tar.gz

That URL always serves the current release, so the same command upgrades an existing install. The versioned archive is published alongside it as sandbox_as_a_service-0.2.0.tar.gz if you pin by name. Prefer a virtual environment or a container if the interpreter is externally managed (see running commands for why a bare pip install is refused on some systems).

Quickstart

Set AAS_API_KEY from Dashboard → API keys and the client picks it up. Creating a sandbox blocks until the machine is ready, and using it as a context manager destroys it on the way out — billing stops there.

from sandbox_as_a_service import Client

client = Client()  # reads AAS_API_KEY from the environment

with client.create_sandbox(size="small", timeout_minutes=15) as sandbox:
    result = sandbox.exec('python3 -c "print(6 * 7)"')
    print(result.stdout, end="")   # 42

    sandbox.write_file("/workspace/app.py", "print('hello from the sandbox')")
    print(sandbox.read_file("/workspace/app.py").content)

    for entry in sandbox.list_files("/workspace").entries:
        print(entry["name"], entry["type"], entry["size_bytes"])
# the sandbox is destroyed here

result.ok is True when the exit code is zero, and result.check() raises ExecutionFailed when it is not — useful when a failed command should stop the script:

result = sandbox.exec("python3 -m pytest -q", timeout_ms=300000, cwd="/workspace")
result.check()  # raises ExecutionFailed on a non-zero exit code

Streaming output

Pass on_stdout or on_stderr and the same call streams: the callbacks fire as the sandbox produces output, and the return value is the same Execution a blocking call returns. Behind the scenes the call switches to the API's event stream.

import sys

result = sandbox.exec(
    "for i in 1 2 3; do echo tick $i; sleep 1; done",
    on_stdout=lambda chunk: print(chunk, end=""),
    on_stderr=lambda chunk: print(chunk, end="", file=sys.stderr),
)
# callbacks fire as output arrives; result is the usual Execution object

Closing the connection mid-stream — the process exiting, Ctrl-C — kills the remote command and records it as cancelled; see streaming output for the full event contract, the heartbeat and the 1 MiB storage-cap rule.

A sandbox can also be created, kept and destroyed explicitly, which is what you want when one machine serves several steps:

sandbox = client.create_sandbox(size="small", name="build-runner", timeout_minutes=15)
try:
    sandbox.exec("git clone https://example.com/repo.git /workspace/repo")
    sandbox.extend(additional_minutes=30)
finally:
    sandbox.destroy()

Pass idempotency_key when a request might be retried — a dropped response then returns the existing sandbox instead of creating a second one: client.create_sandbox(idempotency_key="job-42-attempt-1"). If it is omitted, the client generates a random one per call.

What the client covers

CallWhat it does
client.create_sandbox(size, name, timeout_minutes)Creates a sandbox and returns it ready to use. Idempotency key optional.
client.get_sandbox(id)Fetches one sandbox by id.
client.list_sandboxes(limit, starting_after, include_deleted)One page of sandboxes, newest first.
client.iter_sandboxes()Iterates every sandbox across pages.
client.get_account()Balance, limits and current pricing.
client.get_usage(days=30)Usage and spend over a window.
sandbox.refresh()Re-reads status, expiry and resources.
sandbox.extend(additional_minutes)Pushes the expiry back, within the maximum lifetime.
sandbox.exec(command, timeout_ms, cwd, env)Runs a command and returns stdout, stderr, exit code and duration.
sandbox.get_execution(id)Fetches an execution by id.
sandbox.write_file(path, content, encoding)Writes text or base64 content into the sandbox.
sandbox.read_file(path, encoding)Reads a file back, as text or base64.
sandbox.list_files(path, recursive)Lists a directory with names, types and sizes.
sandbox.delete_file(path, recursive)Deletes a file or directory.
sandbox.expose_port(port) / list_ports() / close_port(port)Creates and manages preview URLs.
sandbox.destroy()Destroys the sandbox. Billing stops immediately.

Errors

Non-2xx responses raise a specific exception, so a caller can react to the reason rather than parse a status code. Every SandboxApiError carries status, type, request_id and the decoded body; quote the request id in a support request.

from sandbox_as_a_service import Client, RateLimitError, NotFoundError

client = Client()

try:
    sandbox = client.get_sandbox("sbx_does_not_exist")
except NotFoundError:
    print("gone")
except RateLimitError as err:
    print("slow down, retry after", err.retry_after, "seconds")
ExceptionRaised when
AuthenticationErrorThe key is missing, malformed or revoked (401).
PermissionError_The key is valid but not allowed to do this (403).
NotFoundErrorNo such sandbox, file or execution (404).
InvalidRequestErrorThe request body or parameters were rejected (400/422).
ConflictErrorThe sandbox is in a state that forbids the operation (409).
PaymentRequiredErrorThe account has no credit left (402).
RateLimitErrorA rate limit was hit (429); retry_after is set when the header is present.
ServiceUnavailableErrorA transient server or upstream failure (5xx).
SandboxConnectionErrorThe request never reached the API — DNS, TLS or timeout.

JavaScript / TypeScript

A zero-dependency Node.js client (Node.js 18+, ESM and CommonJS, hand-written TypeScript definitions) covering the same operations. It is published on npm: www.npmjs.com/package/sandbox-as-a-service.

npm install sandbox-as-a-service

Quickstart

Set AAS_API_KEY from Dashboard → API keys and the client picks it up. Creating a sandbox blocks until the machine is ready, and destroy() stops the billing.

import { Client } from 'sandbox-as-a-service';

const client = new Client(); // reads AAS_API_KEY from the environment

const sandbox = await client.createSandbox({ size: 'small', timeoutMinutes: 10 });
try {
  const result = await sandbox.exec('python3 -c "print(6 * 7)"');
  console.log(result.stdout); // 42

  await sandbox.writeFile('/workspace/app.py', "print('hello from the sandbox')");
  console.log((await sandbox.readFile('/workspace/app.py')).content);
} finally {
  await sandbox.destroy();
}

TypeScript works out of the box — the package ships its own type definitions:

import { Client, ExecutionFailed } from 'sandbox-as-a-service';

const client = new Client(); // reads AAS_API_KEY from the environment
const sandbox = await client.createSandbox({ size: 'small', timeoutMinutes: 10 });
try {
  const result = await sandbox.exec('python3 -m pytest -q', { timeoutMs: 300000, cwd: '/workspace' });
  result.check(); // throws ExecutionFailed on a non-zero exit code
} catch (err) {
  if (err instanceof ExecutionFailed) console.error(err.execution.stderr);
} finally {
  await sandbox.destroy();
}

Pass onStdout or onStderr to stream output as it arrives, and on Node.js 22+ await using sandbox = await client.createSandbox() destroys the sandbox when the scope ends — the same contract as the Python context manager. Pass idempotencyKey to make a retried create return the original sandbox; a random key is generated per call when it is omitted.

Notes

  • Client() reads AAS_API_KEY; pass api_key= or base_url= to override. A bare host is normalized to its /v1 API root.
  • The default client timeout is 600 seconds, generous because create blocks until the machine is ready. exec raises its own timeout above the command's timeout_ms so a long command is not cut off by the client first.
  • Release with the context managers or by calling destroy() — an abandoned sandbox keeps billing until it expires.
  • The core operations are available over REST and as MCP tools; snapshots are available over REST only for now. The SDK is a thin, typed wrapper over the calls it covers, and the OpenAPI document can generate a client in another language.

Try it with your own key

Sign in, create a key, then pip install the client and run your first sandbox.