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
| Call | What 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")
| Exception | Raised when |
|---|---|
AuthenticationError | The key is missing, malformed or revoked (401). |
PermissionError_ | The key is valid but not allowed to do this (403). |
NotFoundError | No such sandbox, file or execution (404). |
InvalidRequestError | The request body or parameters were rejected (400/422). |
ConflictError | The sandbox is in a state that forbids the operation (409). |
PaymentRequiredError | The account has no credit left (402). |
RateLimitError | A rate limit was hit (429); retry_after is set when the header is present. |
ServiceUnavailableError | A transient server or upstream failure (5xx). |
SandboxConnectionError | The 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()readsAAS_API_KEY; passapi_key=orbase_url=to override. A bare host is normalized to its/v1API root.- The default client timeout is 600 seconds, generous because create blocks until the machine
is ready.
execraises its own timeout above the command'stimeout_msso 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.