Skip to content

Sandbox SDK for Python and TypeScript

The sandbox SDKs drive the Agent GPU Sandbox from your own code instead of through an AI connection. One call deploys a GPU VM with the sandbox on and waits until it is ready. Then you run shell commands on it, stream their output while they run, and read and write files, all with a GPU VM API key. This page covers installing the SDKs, creating a sandbox, commands, files, lifecycle and errors.

Use it from training scripts, notebooks, CI jobs and agent frameworks. A sandbox is a real VM that you keep for hours, so files, packages and models stay on it between jobs, and your own SSH key keeps working the whole time. A VM takes a few minutes to boot, so the sandbox suits training, fine-tuning, evals and benchmarks, not quick one-off snippets.

Prerequisites

  • A GPU VM API key from Settings > API Keys. See User settings.
  • An SSH key saved in Settings > SSH Keys, or a public key to pass for one instance. The sandbox never replaces your own access.
  • Credit on your account for the GPU time. See Billing.
  • Python 3.9 or later, or Node.js 18 or later (Bun and Deno also work).

Install the SDK

Python
pip install spheron-sandbox

Export your API key. Both SDKs read it from SPHERON_API_KEY:

export SPHERON_API_KEY=<your-api-key>

Quick start

This example deploys the cheapest H100 offer up to $3 per hour, runs a training job and terminates the instance:

Python
from spheron_sandbox import Sandbox
 
# Deploys and waits until commands can run (a few minutes)
sandbox = Sandbox.create(gpu="h100", gpu_count=1, max_hourly_price=3)
 
print(sandbox.commands.run("nvidia-smi -L").output)
 
sandbox.files.write("/root/train.py", open("train.py").read())
job = sandbox.commands.start("cd /root && python train.py", timeout_sec=6 * 3600)
for chunk in job.stream():
    print(chunk, end="")
 
metrics = sandbox.files.read("/root/metrics.json")
sandbox.terminate()

The instance bills at its normal hourly price from the moment it runs until you stop or terminate it. See Billing.

Create a sandbox

Sandbox.create() lists the GPU offers, deploys the cheapest one that matches your filter with the sandbox on, and waits until it is ready. When an offer is sold out or cannot take the sandbox, it moves on to the next one. A refused offer creates nothing and bills nothing.

PythonTypeScriptDefaultDescription
gpugpuanyGPU model as one word: h100, a100, l40s, rtx4090
gpu_countgpuCountanyGPUs per instance
max_hourly_pricemaxHourlyPricenoneMost to pay per hour for the whole instance, in USD
spotspotFalseSpot offers only. Cheaper, and can be reclaimed at any time. Without it, only dedicated offers are used.
regions, providersregions, providersallNarrow the search
offerofferDeploy this exact offer from find_offers()
operating_systemoperatingSystemUbuntu with CUDAMatched against the offer's images
team_idteamIdyour only teamRequired when your key reaches more than one team
ssh_key_idsshKeyIdfirst saved keyA saved SSH key
ssh_public_keysshPublicKeyA public key for this instance only. Not saved.
volume_idsvolumeIdsVolumes to attach at launch
wait_until_readywaitUntilReadyTrueReturn as soon as the deploy is accepted when false
ready_timeout (seconds)readyTimeoutMs20 minutesHow long to wait for the sandbox
on_statusonStatusCalled with deploying, installing, then ready

Pick an offer yourself

List the offers first, then deploy the one you want:

offers = Sandbox.find_offers(gpu="a100", max_hourly_price=2)
for o in offers:
    print(o["provider"], o["gpuCount"], o["clusters"], o["price"])
 
sandbox = Sandbox.create(offer=offers[0])

Connect to an existing sandbox

Pick up a sandbox again by its deployment ID, from any process or machine:

sandbox = Sandbox.connect("<deployment-id>")

This works for any instance you deployed with the sandbox on, through the SDK, the API, or your AI agent.

Run commands

Commands run with bash as the instance's login user, with sudo available. Output is stdout and stderr together, up to 1 MB per command. The full log stays on the instance at the path in log_path (logPath in TypeScript). Read it with sandbox.files.read(r.log_path).

A finished command's status is done when it exits 0. It is failed for a non-zero exit (with the exit code), for a command killed by its timeout (error is timed out), or for a command the instance lost. r.ok is true only for done with exit code 0.

# Wait for it to finish. Killed after timeout_sec (default 600 seconds).
r = sandbox.commands.run("pip install -r requirements.txt", cwd="/root/app", timeout_sec=1800)
print(r.exit_code, r.output)
 
# Raise CommandExitError unless it exits 0
sandbox.commands.run("pytest -q", check=True)
 
# Print output while it runs
sandbox.commands.run("python bench.py", on_output=lambda chunk: print(chunk, end=""))
 
# Pass environment variables
sandbox.commands.run("python eval.py", env={"HF_TOKEN": "<your-token>"})

Run long jobs in the background

start() returns at once and keeps the command running for up to 86,400 seconds (one day). Use the handle to stream its output, wait for it, or stop it:

job = sandbox.commands.start("python train.py")
print(job.id)               # keep it to come back later
 
for chunk in job.stream():  # stop iterating any time; the job keeps running
    print(chunk, end="")
 
job.wait(timeout=60)        # raises TimeoutError after 60 seconds; the job keeps running
job.kill()                  # stops it and every process it started

Pick a job up again later, for example from another script:

job = sandbox.commands.get("<command-id>")
result = job.wait()

A command sent while the instance is briefly unreachable stays queued and runs once the sandbox is back. Do not send it again.

Read and write files

sandbox.files.write("/root/config.yaml", "lr: 0.001\n")
sandbox.files.write("/root/run.sh", script, mode="0755")
text = sandbox.files.read("/root/out.txt")
data = sandbox.files.read_bytes("/root/plot.png")
entries = sandbox.files.list("/root")        # name, type and size of each entry
sandbox.files.exists("/root/checkpoint.pt")

Paths are absolute. File operations run as root, and a file you write belongs to the instance's login user. Missing parent directories are created. Each read or write moves up to 5 MB, and an account moves at most two files at once (the SDK waits and retries). Download datasets and models on the instance itself:

sandbox.commands.run(
    "huggingface-cli download meta-llama/Llama-3.1-8B --local-dir /root/model",
    timeout_sec=3600,
)

Manage the instance

PythonTypeScriptWhat it does
status()status()Sandbox state: installing, ready, failed or removed, and whether the instance is reachable
info()info()The deployment: status, IP address, SSH command, GPU, hourly rate
stop()stop()Stops the instance and waits until the provider has stopped it. It keeps its disk and bills only storage.
start()start()Starts it again and waits until the sandbox is reachable. Retries for up to 10 minutes while the provider cannot start it yet, for example while a large machine is still saving its memory to disk.
terminate()terminate()Deletes the instance and its disk. Billing stops.
remove_sandbox()removeSandbox()Uninstalls the sandbox program. The instance keeps running.
close()Closes the HTTP connection. The instance keeps running.

An instance must run for its offer's minimum runtime before you can terminate it. Before then, terminate() raises an error with the code minimum_runtime_not_met.

Use asyncio

The Python package has an asyncio client with the same methods:

from spheron_sandbox import AsyncSandbox
 
sandbox = await AsyncSandbox.create(gpu="h100", max_hourly_price=3)
r = await sandbox.commands.run("python eval.py")
job = await sandbox.commands.start("python train.py")
async for chunk in job.stream():
    print(chunk, end="")
await sandbox.terminate()

Handle errors

Every error extends SpheronError. Refusals from the API are SpheronApiError, with the HTTP status, the error code and the request ID.

ErrorWhen
AuthenticationErrorThe API key is missing, wrong, or an Inference API key
NotFoundErrorNo such instance, no sandbox on it, or a teammate deployed it
InstanceNotRunningErrorThe instance is stopped, terminated or failed
SandboxNotReadyErrorThe sandbox is still installing
SandboxUnreachableErrorSpheron cannot reach the instance. recovery holds a command to run over SSH. From commands.run() it also names the queued command (command_id, commandId in TypeScript).
SandboxFailedErrorThe install failed or the instance died while you waited
CommandExitErrorcheck is on and the command exited non-zero
NoOfferErrorNo offer matched, or every one tried was refused
TimeoutErrorA wait in the SDK ran out. The instance or command keeps running.

The SDKs retry a safe request that hit a dropped connection or a 502, 503 or 504 with backoff (max_retries, maxRetries in TypeScript, default 3). A request refused with 429 is retried for up to 90 seconds, waiting as long as the API asks. File transfers queue in the SDK: at most 2 run at once per sandbox, the server's limit.

request_timeout (requestTimeoutMs in TypeScript) bounds a single HTTP request. Waits such as wait_until_ready() and job.wait() take their own timeout.

Use the REST API directly

The SDKs are built on the public API. To do the same with any HTTP client:

  1. Deploy with "agentAccess": true on POST /api/deployments.
  2. Wait until GET /api/deployments/{deploymentId}/agent shows "state": "ready" and "reachable": true.
  3. Run commands with POST /api/deployments/{deploymentId}/agent/commands.
curl -X POST https://app.spheron.ai/api/deployments/<deployment-id>/agent/commands \
  -H "Authorization: Bearer $SPHERON_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"command": "nvidia-smi -L", "waitSec": 30}'

Billing

The sandbox is free. You pay for the GPU instance at its normal price until you stop or terminate it, exactly as without the sandbox. A stopped instance bills only storage. If create() raises after the instance was made, for example because the install failed, the error's deployment_id (deploymentId in TypeScript) names the instance: terminate it if you no longer need it.

What's next

Troubleshooting

Issue: The deploy is refused with agent_access_unsupported

Symptoms: create() with a hand-picked offer raises SpheronApiError with the code agent_access_unsupported.

Diagnosis: The offer accepts neither a startup script nor SSH setup, so the sandbox program cannot be installed.

Resolution: Pick an offer from another provider, or call create() with a filter instead of an offer so it moves on by itself.

Issue: create() raises SandboxFailedError

Symptoms: The instance was deployed, but create() raises SandboxFailedError.

Diagnosis: The sandbox install failed, or the instance failed while booting. The error's message says which.

Resolution: If the error has a recovery_command, SSH into the instance and run it. Otherwise terminate the instance with its deployment_id and create a new one.

Issue: Commands raise SandboxUnreachableError

Symptoms: File operations or commands.run() raise SandboxUnreachableError, or commands stay queued.

Diagnosis: The sandbox program stopped, or the instance lost its network connection.

Resolution: SSH into the instance and run the command in the error's recovery. Queued commands run once the sandbox is back. See Restore the sandbox.

Issue: NotFoundError on an instance you can see in the dashboard

Symptoms: Sandbox.connect() raises NotFoundError for an instance in your team.

Diagnosis: Only the person who deployed an instance can use its sandbox, and only if it was deployed with the sandbox on.

Resolution: Use the API key of the person who deployed it, or create a new sandbox.