Python SDK Reference
Use the Vercel Sandbox Python SDK to create ephemeral Linux microVMs, run commands, manage files, and capture snapshots from sync or async Python code.
For JavaScript, see the JS SDK Reference.
Install the Python package:
uv add vercelAfter installation:
- Link your project and pull environment variables with
vercel linkandvercel env pullso the SDK can read a Vercel OpenID Connect (OIDC) token. - Choose the sandbox runtime your workload needs.
- Import from
vercel.sandboxfor the full sync and async API surface. If you want async-first aliases for the main sandbox and command types,vercel.sandbox.aioexports asyncSandbox,Command, andCommandFinishedaliases.
| Class | What it does | Example |
|---|---|---|
AsyncSandbox | Creates and manages sandboxes in async Python code | async with await AsyncSandbox.create() as sandbox: |
AsyncCommand | Represents a running or finished command in async code | command = await sandbox.run_command_detached(...) |
AsyncSnapshot | Represents a saved sandbox state in async code | snapshot = await sandbox.snapshot() |
Sandbox | Creates and manages sandboxes in sync Python code | with Sandbox.create() as sandbox: |
Command | Represents a running or finished command in sync code | command = sandbox.run_command_detached(...) |
Snapshot | Represents a saved sandbox state in sync code | snapshot = sandbox.snapshot() |
The Sandbox and AsyncSandbox classes manage the full sandbox lifecycle. The sync API returns values directly, and the async API returns awaitables and async iterators.
Use sandbox_id to identify the current microVM so you can reconnect with Sandbox.get() or AsyncSandbox.get(). Store the value when your workflow spans multiple processes, retries, or background workers.
Returns: str.
print(sandbox.sandbox_id)The status accessor reports the lifecycle state of the sandbox. Use it to check whether a sandbox is ready for new work, still starting, or already stopped.
Returns: SandboxStatus.
Possible values:
SandboxStatus.PENDINGSandboxStatus.RUNNINGSandboxStatus.STOPPINGSandboxStatus.STOPPEDSandboxStatus.ABORTEDSandboxStatus.FAILEDSandboxStatus.SNAPSHOTTING
from vercel.sandbox import SandboxStatus
if sandbox.status == SandboxStatus.RUNNING:
print("Sandbox is ready")
print(sandbox.status)Use source_snapshot_id to inspect which snapshot created the current sandbox. This value is None when the sandbox did not start from a snapshot.
Returns: str | None.
print(sandbox.source_snapshot_id)The timeout accessor returns the current sandbox timeout in milliseconds. Compare this with upcoming work and call extend_timeout() when you need more time.
Returns: int.
print(sandbox.timeout)Use network_policy to inspect the current egress policy on the sandbox. This is useful when you update firewall rules dynamically during a workflow.
Returns: NetworkPolicy | None.
print(sandbox.network_policy)The interactive_port accessor returns the PTY port when the sandbox was created with interactive=True. It remains None for non-interactive sandboxes.
Returns: int | None.
print(sandbox.interactive_port)Use create() to launch a new sandbox with the runtime, source, timeout, ports, and network policy your workflow needs. The async version returns an awaitable sandbox and supports the same parameters.
Returns: Sandbox for sync code and AsyncSandbox for async code.
| Parameter | Type | Required | Details |
|---|---|---|---|
source | Source | None | No | Use a Git repository, tarball, or snapshot as the starting filesystem. |
ports | list[int] | None | No | Ports to expose through sandbox.domain(port). |
timeout | int | None | No | Initial timeout in milliseconds. |
resources | dict[str, Any] | None | No | Resource configuration such as virtual CPUs. |
runtime | str | None | No | Runtime image such as node24, node22, or python3.13. |
token | str | None | No | Access token override. |
project_id | str | None | No | Project scope override. |
team_id | str | None | No | Team scope override. |
interactive | bool | No | Enables PTY support. Use the async API for shell(). |
env | dict[str, str] | None | No | Default environment variables for commands. |
network_policy | NetworkPolicy | None | No | Egress policy, including "allow-all", "deny-all", or NetworkPolicyCustom(...). |
Create a sandbox from a snapshot:
import asyncio
from vercel.sandbox import AsyncSandbox
async def main() -> None:
async with await AsyncSandbox.create(
source={"type": "snapshot", "snapshot_id": "snp_123"},
timeout=120_000,
) as sandbox:
print(sandbox.source_snapshot_id)
asyncio.run(main())Create a sandbox from a Git repository:
import asyncio
from vercel.sandbox import AsyncSandbox
async def main() -> None:
async with await AsyncSandbox.create(
source={
"type": "git",
"url": "https://github.com/vercel/examples.git",
"revision": "main",
"depth": 1,
},
runtime="python3.13",
) as sandbox:
result = await sandbox.run_command("python3", ["--version"])
print(await result.stdout())
asyncio.run(main())Use get() to reconnect to an active sandbox by ID. This is useful when a background job stores sandbox_id and resumes work later.
Returns: Sandbox for sync code and AsyncSandbox for async code.
| Parameter | Type | Required | Details |
|---|---|---|---|
sandbox_id | str | Yes | Identifier of the sandbox to retrieve. |
token | str | None | No | Access token override. |
project_id | str | None | No | Project scope override. |
team_id | str | None | No | Team scope override. |
import asyncio
from vercel.sandbox import AsyncSandbox
async def main() -> None:
sandbox = await AsyncSandbox.get(sandbox_id="sbx_123")
print(sandbox.status)
asyncio.run(main())Use list() to fetch sandbox summaries for a project.
Returns: The first page of sandbox summaries. In Python, the returned page is iterable, so you can loop over its items directly.
| Parameter | Type | Required | Details |
|---|---|---|---|
limit | int | None | No | Maximum number of sandboxes to return per page. |
since | datetime | int | None | No | Lower timestamp bound as a timezone-aware datetime or epoch ms. |
until | datetime | int | None | No | Upper timestamp bound as a timezone-aware datetime or epoch ms. |
token | str | None | No | Access token override. |
project_id | str | None | No | Project scope override. |
team_id | str | None | No | Team scope override. |
import asyncio
from vercel.sandbox import AsyncSandbox
async def main() -> None:
page = await AsyncSandbox.list(limit=10)
async for sandbox in page:
print(sandbox.sandbox_id)
asyncio.run(main())Use refresh() to reload the current sandbox state from the API. Call it when another process may have changed the sandbox and you need the latest values for accessors such as status or timeout.
Returns: None.
await sandbox.refresh()Use wait_for_status() to poll until the sandbox reaches a specific lifecycle state. Use it when a background job must wait for a sandbox to finish starting or stopping before continuing.
Returns: None.
| Parameter | Type | Required | Details |
|---|---|---|---|
status | SandboxStatus | str | Yes | Target lifecycle state to wait for. Use any SandboxStatus value, such as SandboxStatus.RUNNING. |
timeout | float | No | Maximum time to wait, in seconds. |
poll_interval | float | No | Delay between status checks. |
from vercel.sandbox import SandboxStatus
await sandbox.wait_for_status(SandboxStatus.RUNNING, timeout=30.0)Use domain() to get the public URL for an exposed port. The port must be included in ports when you create the sandbox.
The call pattern is the same for sync and async sandbox objects.
Returns: str.
| Parameter | Type | Required | Details |
|---|---|---|---|
port | int | Yes | Exposed port to resolve. |
print(sandbox.domain(3000))Use get_command() to fetch a previously started command by ID. This is useful after detached command execution when you want to resume log streaming or wait for completion later.
Returns: Command for sync code and AsyncCommand for async code.
| Parameter | Type | Required | Details |
|---|---|---|---|
cmd_id | str | Yes | Identifier of the command to fetch. |
command = await sandbox.get_command("cmd_123")
print(command.cmd_id)Use run_command() to execute a command and wait for it to finish. The async version returns an awaitable result with the same arguments and behavior.
Returns: CommandFinished for sync code and AsyncCommandFinished for async code.
| Parameter | Type | Required | Details |
|---|---|---|---|
cmd | str | Yes | Command to execute. |
args | list[str] | None | No | Arguments for the command. |
cwd | str | None | No | Working directory for execution. |
env | dict[str, str] | None | No | Additional environment variables. |
sudo | bool | No | Run the command with elevated privileges. |
result = await sandbox.run_command(
"python3",
["--version"],
env={"PYTHONUNBUFFERED": "1"},
)
print(result.exit_code)
print(await result.stdout())Use run_command_detached() to start a command and return immediately with a live command object. This is useful for long-running processes, streaming logs, or waiting for results later.
Returns: Command for sync code and AsyncCommand for async code.
| Parameter | Type | Required | Details |
|---|---|---|---|
cmd | str | Yes | Command to execute. |
args | list[str] | None | No | Arguments for the command. |
cwd | str | None | No | Working directory for execution. |
env | dict[str, str] | None | No | Additional environment variables. |
sudo | bool | No | Run the command with elevated privileges. |
command = await sandbox.run_command_detached(
"bash",
["-lc", "for i in 1 2 3; do echo $i; sleep 1; done"],
)
print(command.cmd_id)Use mk_dir() to create a directory in the sandbox filesystem before writing files or cloning repositories into it.
Returns: None.
| Parameter | Type | Required | Details |
|---|---|---|---|
path | str | Yes | Directory to create. |
cwd | str | None | No | Base directory for path. |
await sandbox.mk_dir("assets")Use iter_file() to stream file contents from the sandbox in chunks. This is useful when you want to process a large file without loading it fully into memory.
Returns: An iterator for sync code and an async iterator for async code.
| Parameter | Type | Required | Details |
|---|---|---|---|
path | str | Yes | Path to the file inside the sandbox. |
cwd | str | None | No | Base directory for resolving path. |
chunk_size | int | No | Number of bytes per chunk. |
stream = await sandbox.iter_file("package.json")
async for chunk in stream:
print(chunk)Use read_file() to read an entire file into memory. The method returns None when the path does not exist.
Returns: bytes | None.
| Parameter | Type | Required | Details |
|---|---|---|---|
path | str | Yes | Path to the file inside the sandbox. |
cwd | str | None | No | Base directory for resolving path. |
contents = await sandbox.read_file("package.json")
print(contents)Use download_file() to copy a file from the sandbox to your local filesystem. Set create_parents=True when the local destination directory may not exist yet.
Returns: str.
| Parameter | Type | Required | Details |
|---|---|---|---|
remote_path | str | Yes | Path to the file inside the sandbox. |
local_path | str | Yes | Destination path on your local machine. |
cwd | str | None | No | Base directory for resolving remote_path. |
create_parents | bool | No | Create parent directories for the local file. |
chunk_size | int | No | Number of bytes per chunk while downloading. |
await sandbox.download_file(
"dist/app.tar.gz",
"./artifacts/app.tar.gz",
create_parents=True,
)Use write_files() to upload one or more files into the sandbox. Each file entry requires a sandbox path and binary content, with an optional Unix mode for file permissions.
Returns: None.
| Parameter | Type | Required | Details |
|---|---|---|---|
files | list[WriteFile] | Yes | Files to write into the sandbox. |
files.path | str | Yes | Path to the file inside the sandbox. |
files.content | bytes | Yes | File contents as bytes. |
files.mode | int | No | Unix file permissions such as 0o755 for an executable script. |
await sandbox.write_files(
[{"path": "config.json", "content": b'{"env": "prod"}'}]
)Use update_network_policy() to replace the sandbox egress policy after creation. Use it when a workflow needs broader network access for a specific step and you want to restore restrictions afterward.
Returns: NetworkPolicy.
| Parameter | Type | Required | Details |
|---|---|---|---|
network_policy | NetworkPolicy | Yes | New egress policy for the sandbox. |
await sandbox.update_network_policy("deny-all")Use extend_timeout() to add more time to a running sandbox before it stops automatically.
Returns: None.
| Parameter | Type | Required | Details |
|---|---|---|---|
duration | int | Yes | Timeout extension in milliseconds. |
await sandbox.extend_timeout(60_000)Use stop() to shut down the sandbox. Set blocking=True when you want to wait until the stop operation completes before continuing.
Returns: None.
| Parameter | Type | Required | Details |
|---|---|---|---|
blocking | bool | No | Wait for the sandbox to stop. |
timeout | float | No | Maximum time to wait, in seconds. |
poll_interval | float | No | Delay between status checks. |
await sandbox.stop(blocking=True)Use snapshot() to save the sandbox filesystem so you can restore it later. Creating a snapshot stops the source sandbox.
Returns: Snapshot for sync code and AsyncSnapshot for async code.
| Parameter | Type | Required | Details |
|---|---|---|---|
expiration | int | None | No | Expiration time in milliseconds. Use 0 for no expiration. Expiring snapshots must use MIN_SNAPSHOT_EXPIRATION_MS (86_400_000, 24 hours) or greater. |
snapshot = await sandbox.snapshot()
print(snapshot.snapshot_id)Use shell() to start an interactive PTY session inside the sandbox. This method is available only on AsyncSandbox, and the sandbox must be created with interactive=True.
Returns: None.
| Parameter | Type | Required | Details |
|---|---|---|---|
command | list[str] | None | No | Command to launch in the PTY session. |
env | dict[str, str] | None | No | Environment variables for the shell. |
cwd | str | None | No | Working directory for the shell. |
sudo | bool | No | Run the shell with elevated privileges. |
Interactive shells require interactive=True and the async API:
import asyncio
from vercel.sandbox import AsyncSandbox
async def main() -> None:
sandbox = await AsyncSandbox.create(interactive=True, timeout=300_000)
try:
await sandbox.shell(["/bin/bash"])
finally:
await sandbox.stop()
asyncio.run(main())The Command and AsyncCommand classes represent detached commands that are still running or can be inspected later. Use them to stream logs, wait for completion, or terminate a process.
Use cmd_id to identify a detached command so you can fetch it later with get_command().
Returns: str.
print(command.cmd_id)The cwd accessor returns the working directory used for the command.
Returns: str.
print(command.cwd)Use started_at to inspect when the command began. The value is a Unix timestamp in milliseconds.
Returns: int.
print(command.started_at)Use logs() to stream LogLine records from the command. The sync API returns an iterator, and the async API returns an async iterator.
Returns: An iterator for sync code and an async iterator for async code.
async for line in command.logs():
print(line.stream, line.data, end="")Use wait() to block until the detached command finishes unless it has already completed.
Returns: CommandFinished for sync code and AsyncCommandFinished for async code.
finished = await command.wait()
print(finished.exit_code)Use output() to collect the command output as a string. You can request stdout, stderr, or both streams.
Returns: str.
| Parameter | Type | Required | Details |
|---|---|---|---|
stream | str | No | Output stream to collect, such as both, stdout, or stderr. |
print(await command.output(stream="both"))Use stdout() to return only standard output collected for the command.
Returns: str.
print(await command.stdout())Use stderr() to return only standard error collected for the command.
Returns: str.
print(await command.stderr())Use kill() to send a signal to the running command.
Returns: None.
| Parameter | Type | Required | Details |
|---|---|---|---|
signal | int | No | POSIX signal number to send. Defaults to 15 (SIGTERM). Use 9 (SIGKILL) to force-terminate the process. |
await command.kill(signal=15)import asyncio
from vercel.sandbox import AsyncSandbox
async def main() -> None:
async with await AsyncSandbox.create(timeout=60_000) as sandbox:
command = await sandbox.run_command_detached(
"bash",
["-lc", "for i in 1 2 3; do echo $i; sleep 1; done"],
)
async for line in command.logs():
print(line.stream, line.data, end="")
finished = await command.wait()
print(finished.exit_code)
asyncio.run(main())CommandFinished and AsyncCommandFinished represent commands that have already completed. They inherit the Command and AsyncCommand methods logs(), wait(), output(), stdout(), stderr(), and kill(), so you can inspect output and metadata after the process exits.
Use exit_code to inspect the final process status. A value of 0 means the command succeeded. Any non-zero value means it failed.
The accessor is the same for sync and async command results.
Returns: int.
if result.exit_code == 0:
print("Command succeeded")The Snapshot and AsyncSnapshot classes save a sandbox filesystem so you can restore it later. Creating a snapshot stops the source sandbox.
Use snapshot_id to identify the snapshot for later restore or deletion.
Returns: str.
print(snapshot.snapshot_id)The source_sandbox_id accessor returns the sandbox ID that created the snapshot.
Returns: str.
print(snapshot.source_sandbox_id)Use status to inspect the snapshot lifecycle state.
Returns: "created" | "deleted" | "failed".
print(snapshot.status)The size_bytes accessor returns the snapshot size in bytes.
Returns: int.
print(snapshot.size_bytes)Use created_at to inspect when the snapshot was created. The value is a Unix timestamp in milliseconds.
Returns: int.
print(snapshot.created_at)Use expires_at to inspect when the snapshot expires. The value is None when the snapshot does not expire.
Returns: int | None.
print(snapshot.expires_at)Use get() to fetch an existing snapshot by ID.
Returns: Snapshot for sync code and AsyncSnapshot for async code.
| Parameter | Type | Required | Details |
|---|---|---|---|
snapshot_id | str | Yes | Identifier of the snapshot to retrieve. |
token | str | None | No | Access token override. |
project_id | str | None | No | Project scope override. |
team_id | str | None | No | Team scope override. |
import asyncio
from vercel.sandbox import AsyncSnapshot
async def main() -> None:
snapshot = await AsyncSnapshot.get(snapshot_id="snp_123")
print(snapshot.status)
asyncio.run(main())Use list() to fetch snapshot summaries for a project.
Returns: The first page of snapshot summaries. In Python, the returned page is iterable, so you can loop over its items directly.
| Parameter | Type | Required | Details |
|---|---|---|---|
limit | int | None | No | Maximum number of snapshots to return per page. |
since | datetime | int | None | No | Lower timestamp bound as a timezone-aware datetime or epoch ms. |
until | datetime | int | None | No | Upper timestamp bound as a timezone-aware datetime or epoch ms. |
token | str | None | No | Access token override. |
project_id | str | None | No | Project scope override. |
team_id | str | None | No | Team scope override. |
import asyncio
from vercel.sandbox import AsyncSnapshot
async def main() -> None:
page = await AsyncSnapshot.list(limit=10)
async for snapshot in page:
print(snapshot.snapshot_id)
asyncio.run(main())Use delete() to remove a snapshot you no longer need.
Returns: None.
await snapshot.delete()Use SnapshotExpiration(value) to validate snapshot expiration values before you pass them to snapshot().
MIN_SNAPSHOT_EXPIRATION_MS is the minimum expiration for expiring snapshots. The value is 86_400_000 milliseconds, or 24 hours.
import asyncio
from vercel.sandbox import AsyncSandbox, MIN_SNAPSHOT_EXPIRATION_MS
async def main() -> None:
async with await AsyncSandbox.create(timeout=120_000) as sandbox:
await sandbox.write_files(
[{"path": "config.json", "content": b'{"env": "prod"}'}]
)
snapshot = await sandbox.snapshot(
expiration=MIN_SNAPSHOT_EXPIRATION_MS
)
async with await AsyncSandbox.create(
source={"type": "snapshot", "snapshot_id": snapshot.snapshot_id},
timeout=120_000,
) as restored:
print(await restored.read_file("config.json"))
asyncio.run(main())These types are exported from vercel.sandbox:
- Source types:
Source,GitSource,TarballSource, andSnapshotSource - Sandbox types:
SandboxStatusfor sandbox lifecycle states andWriteFileforwrite_files() - Network policy types:
NetworkPolicy,NetworkPolicyCustom,NetworkPolicyRule,NetworkPolicySubnets, andNetworkTransformer - Command result types:
CommandFinishedandAsyncCommandFinished - Error types:
SandboxError,APIError,SandboxAuthError,SandboxNotFoundError,SandboxPermissionError,SandboxRateLimitError, andSandboxServerError
The SDK supports the same authentication methods as the rest of Vercel Sandbox:
- Vercel OIDC tokens through
VERCEL_OIDC_TOKEN. The SDK usesVERCEL_PROJECT_IDandVERCEL_TEAM_IDwhen present, and falls back to decoding them from the token payload when possible. - Access tokens through
token=..., or throughVERCEL_TOKENtogether withVERCEL_PROJECT_IDandVERCEL_TEAM_ID.
For setup details, see Authentication.
Was this helpful?