Skip to content
Menu

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:

Terminal
uv add vercel

After installation:

  • Link your project and pull environment variables with vercel link and vercel env pull so the SDK can read a Vercel OpenID Connect (OIDC) token.
  • Choose the sandbox runtime your workload needs.
  • Import from vercel.sandbox for the full sync and async API surface. If you want async-first aliases for the main sandbox and command types, vercel.sandbox.aio exports async Sandbox, Command, and CommandFinished aliases.
ClassWhat it doesExample
AsyncSandboxCreates and manages sandboxes in async Python codeasync with await AsyncSandbox.create() as sandbox:
AsyncCommandRepresents a running or finished command in async codecommand = await sandbox.run_command_detached(...)
AsyncSnapshotRepresents a saved sandbox state in async codesnapshot = await sandbox.snapshot()
SandboxCreates and manages sandboxes in sync Python codewith Sandbox.create() as sandbox:
CommandRepresents a running or finished command in sync codecommand = sandbox.run_command_detached(...)
SnapshotRepresents a saved sandbox state in sync codesnapshot = 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.

main.py
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.PENDING
  • SandboxStatus.RUNNING
  • SandboxStatus.STOPPING
  • SandboxStatus.STOPPED
  • SandboxStatus.ABORTED
  • SandboxStatus.FAILED
  • SandboxStatus.SNAPSHOTTING
main.py
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.

main.py
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.

main.py
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.

main.py
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.

main.py
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.

ParameterTypeRequiredDetails
sourceSource | NoneNoUse a Git repository, tarball, or snapshot as the starting filesystem.
portslist[int] | NoneNoPorts to expose through sandbox.domain(port).
timeoutint | NoneNoInitial timeout in milliseconds.
resourcesdict[str, Any] | NoneNoResource configuration such as virtual CPUs.
runtimestr | NoneNoRuntime image such as node24, node22, or python3.13.
tokenstr | NoneNoAccess token override.
project_idstr | NoneNoProject scope override.
team_idstr | NoneNoTeam scope override.
interactiveboolNoEnables PTY support. Use the async API for shell().
envdict[str, str] | NoneNoDefault environment variables for commands.
network_policyNetworkPolicy | NoneNoEgress policy, including "allow-all", "deny-all", or NetworkPolicyCustom(...).

Create a sandbox from a snapshot:

main.py
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())
main.py
from vercel.sandbox import Sandbox
 
with Sandbox.create(
    source={"type": "snapshot", "snapshot_id": "snp_123"},
    timeout=120_000,
) as sandbox:
    print(sandbox.source_snapshot_id)

Create a sandbox from a Git repository:

main.py
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())
main.py
from vercel.sandbox import Sandbox
 
with Sandbox.create(
    source={
        "type": "git",
        "url": "https://github.com/vercel/examples.git",
        "revision": "main",
        "depth": 1,
    },
    runtime="python3.13",
) as sandbox:
    result = sandbox.run_command("python3", ["--version"])
    print(result.stdout())

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.

ParameterTypeRequiredDetails
sandbox_idstrYesIdentifier of the sandbox to retrieve.
tokenstr | NoneNoAccess token override.
project_idstr | NoneNoProject scope override.
team_idstr | NoneNoTeam scope override.
main.py
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())
main.py
from vercel.sandbox import Sandbox
 
sandbox = Sandbox.get(sandbox_id="sbx_123")
print(sandbox.status)

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.

ParameterTypeRequiredDetails
limitint | NoneNoMaximum number of sandboxes to return per page.
sincedatetime | int | NoneNoLower timestamp bound as a timezone-aware datetime or epoch ms.
untildatetime | int | NoneNoUpper timestamp bound as a timezone-aware datetime or epoch ms.
tokenstr | NoneNoAccess token override.
project_idstr | NoneNoProject scope override.
team_idstr | NoneNoTeam scope override.
main.py
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())
main.py
from vercel.sandbox import Sandbox
 
page = Sandbox.list(limit=10)
 
for sandbox in page:
    print(sandbox.sandbox_id)

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.

main.py
await sandbox.refresh()
main.py
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.

ParameterTypeRequiredDetails
statusSandboxStatus | strYesTarget lifecycle state to wait for. Use any SandboxStatus value, such as SandboxStatus.RUNNING.
timeoutfloatNoMaximum time to wait, in seconds.
poll_intervalfloatNoDelay between status checks.
main.py
from vercel.sandbox import SandboxStatus
 
await sandbox.wait_for_status(SandboxStatus.RUNNING, timeout=30.0)
main.py
from vercel.sandbox import SandboxStatus
 
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.

ParameterTypeRequiredDetails
portintYesExposed port to resolve.
main.py
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.

ParameterTypeRequiredDetails
cmd_idstrYesIdentifier of the command to fetch.
main.py
command = await sandbox.get_command("cmd_123")
print(command.cmd_id)
main.py
command = 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.

ParameterTypeRequiredDetails
cmdstrYesCommand to execute.
argslist[str] | NoneNoArguments for the command.
cwdstr | NoneNoWorking directory for execution.
envdict[str, str] | NoneNoAdditional environment variables.
sudoboolNoRun the command with elevated privileges.
main.py
result = await sandbox.run_command(
    "python3",
    ["--version"],
    env={"PYTHONUNBUFFERED": "1"},
)
print(result.exit_code)
print(await result.stdout())
main.py
result = sandbox.run_command(
    "python3",
    ["--version"],
    env={"PYTHONUNBUFFERED": "1"},
)
print(result.exit_code)
print(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.

ParameterTypeRequiredDetails
cmdstrYesCommand to execute.
argslist[str] | NoneNoArguments for the command.
cwdstr | NoneNoWorking directory for execution.
envdict[str, str] | NoneNoAdditional environment variables.
sudoboolNoRun the command with elevated privileges.
main.py
command = await sandbox.run_command_detached(
    "bash",
    ["-lc", "for i in 1 2 3; do echo $i; sleep 1; done"],
)
print(command.cmd_id)
main.py
command = 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.

ParameterTypeRequiredDetails
pathstrYesDirectory to create.
cwdstr | NoneNoBase directory for path.
main.py
await sandbox.mk_dir("assets")
main.py
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.

ParameterTypeRequiredDetails
pathstrYesPath to the file inside the sandbox.
cwdstr | NoneNoBase directory for resolving path.
chunk_sizeintNoNumber of bytes per chunk.
main.py
stream = await sandbox.iter_file("package.json")
 
async for chunk in stream:
    print(chunk)
main.py
for chunk in sandbox.iter_file("package.json"):
    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.

ParameterTypeRequiredDetails
pathstrYesPath to the file inside the sandbox.
cwdstr | NoneNoBase directory for resolving path.
main.py
contents = await sandbox.read_file("package.json")
print(contents)
main.py
contents = 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.

ParameterTypeRequiredDetails
remote_pathstrYesPath to the file inside the sandbox.
local_pathstrYesDestination path on your local machine.
cwdstr | NoneNoBase directory for resolving remote_path.
create_parentsboolNoCreate parent directories for the local file.
chunk_sizeintNoNumber of bytes per chunk while downloading.
main.py
await sandbox.download_file(
    "dist/app.tar.gz",
    "./artifacts/app.tar.gz",
    create_parents=True,
)
main.py
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.

ParameterTypeRequiredDetails
fileslist[WriteFile]YesFiles to write into the sandbox.
files.pathstrYesPath to the file inside the sandbox.
files.contentbytesYesFile contents as bytes.
files.modeintNoUnix file permissions such as 0o755 for an executable script.
main.py
await sandbox.write_files(
    [{"path": "config.json", "content": b'{"env": "prod"}'}]
)
main.py
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.

ParameterTypeRequiredDetails
network_policyNetworkPolicyYesNew egress policy for the sandbox.
main.py
await sandbox.update_network_policy("deny-all")
main.py
sandbox.update_network_policy("deny-all")

Use extend_timeout() to add more time to a running sandbox before it stops automatically.

Returns: None.

ParameterTypeRequiredDetails
durationintYesTimeout extension in milliseconds.
main.py
await sandbox.extend_timeout(60_000)
main.py
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.

ParameterTypeRequiredDetails
blockingboolNoWait for the sandbox to stop.
timeoutfloatNoMaximum time to wait, in seconds.
poll_intervalfloatNoDelay between status checks.
main.py
await sandbox.stop(blocking=True)
main.py
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.

ParameterTypeRequiredDetails
expirationint | NoneNoExpiration time in milliseconds. Use 0 for no expiration. Expiring snapshots must use MIN_SNAPSHOT_EXPIRATION_MS (86_400_000, 24 hours) or greater.
main.py
snapshot = await sandbox.snapshot()
print(snapshot.snapshot_id)
main.py
snapshot = 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.

ParameterTypeRequiredDetails
commandlist[str] | NoneNoCommand to launch in the PTY session.
envdict[str, str] | NoneNoEnvironment variables for the shell.
cwdstr | NoneNoWorking directory for the shell.
sudoboolNoRun the shell with elevated privileges.

Interactive shells require interactive=True and the async API:

main.py
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.

main.py
print(command.cmd_id)

The cwd accessor returns the working directory used for the command.

Returns: str.

main.py
print(command.cwd)

Use started_at to inspect when the command began. The value is a Unix timestamp in milliseconds.

Returns: int.

main.py
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.

main.py
async for line in command.logs():
    print(line.stream, line.data, end="")
main.py
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.

main.py
finished = await command.wait()
print(finished.exit_code)
main.py
finished = 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.

ParameterTypeRequiredDetails
streamstrNoOutput stream to collect, such as both, stdout, or stderr.
main.py
print(await command.output(stream="both"))
main.py
print(command.output(stream="both"))

Use stdout() to return only standard output collected for the command.

Returns: str.

main.py
print(await command.stdout())
main.py
print(command.stdout())

Use stderr() to return only standard error collected for the command.

Returns: str.

main.py
print(await command.stderr())
main.py
print(command.stderr())

Use kill() to send a signal to the running command.

Returns: None.

ParameterTypeRequiredDetails
signalintNoPOSIX signal number to send. Defaults to 15 (SIGTERM). Use 9 (SIGKILL) to force-terminate the process.
main.py
await command.kill(signal=15)
main.py
command.kill(signal=15)
main.py
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())
main.py
from vercel.sandbox import Sandbox
 
with Sandbox.create(timeout=60_000) as sandbox:
    command = sandbox.run_command_detached(
        "bash",
        ["-lc", "for i in 1 2 3; do echo $i; sleep 1; done"],
    )
 
    for line in command.logs():
        print(line.stream, line.data, end="")
 
    finished = command.wait()
    print(finished.exit_code)

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.

main.py
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.

main.py
print(snapshot.snapshot_id)

The source_sandbox_id accessor returns the sandbox ID that created the snapshot.

Returns: str.

main.py
print(snapshot.source_sandbox_id)

Use status to inspect the snapshot lifecycle state.

Returns: "created" | "deleted" | "failed".

main.py
print(snapshot.status)

The size_bytes accessor returns the snapshot size in bytes.

Returns: int.

main.py
print(snapshot.size_bytes)

Use created_at to inspect when the snapshot was created. The value is a Unix timestamp in milliseconds.

Returns: int.

main.py
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.

main.py
print(snapshot.expires_at)

Use get() to fetch an existing snapshot by ID.

Returns: Snapshot for sync code and AsyncSnapshot for async code.

ParameterTypeRequiredDetails
snapshot_idstrYesIdentifier of the snapshot to retrieve.
tokenstr | NoneNoAccess token override.
project_idstr | NoneNoProject scope override.
team_idstr | NoneNoTeam scope override.
main.py
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())
main.py
from vercel.sandbox import Snapshot
 
snapshot = Snapshot.get(snapshot_id="snp_123")
print(snapshot.status)

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.

ParameterTypeRequiredDetails
limitint | NoneNoMaximum number of snapshots to return per page.
sincedatetime | int | NoneNoLower timestamp bound as a timezone-aware datetime or epoch ms.
untildatetime | int | NoneNoUpper timestamp bound as a timezone-aware datetime or epoch ms.
tokenstr | NoneNoAccess token override.
project_idstr | NoneNoProject scope override.
team_idstr | NoneNoTeam scope override.
main.py
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())
main.py
from vercel.sandbox import Snapshot
 
page = Snapshot.list(limit=10)
 
for snapshot in page:
    print(snapshot.snapshot_id)

Use delete() to remove a snapshot you no longer need.

Returns: None.

main.py
await snapshot.delete()
main.py
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.

main.py
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())
main.py
from vercel.sandbox import MIN_SNAPSHOT_EXPIRATION_MS, Sandbox
 
with Sandbox.create(timeout=120_000) as sandbox:
    sandbox.write_files(
        [{"path": "config.json", "content": b'{"env": "prod"}'}]
    )
    snapshot = sandbox.snapshot(expiration=MIN_SNAPSHOT_EXPIRATION_MS)
 
with Sandbox.create(
    source={"type": "snapshot", "snapshot_id": snapshot.snapshot_id},
    timeout=120_000,
) as restored:
    print(restored.read_file("config.json"))

These types are exported from vercel.sandbox:

  • Source types: Source, GitSource, TarballSource, and SnapshotSource
  • Sandbox types: SandboxStatus for sandbox lifecycle states and WriteFile for write_files()
  • Network policy types: NetworkPolicy, NetworkPolicyCustom, NetworkPolicyRule, NetworkPolicySubnets, and NetworkTransformer
  • Command result types: CommandFinished and AsyncCommandFinished
  • Error types: SandboxError, APIError, SandboxAuthError, SandboxNotFoundError, SandboxPermissionError, SandboxRateLimitError, and SandboxServerError

The SDK supports the same authentication methods as the rest of Vercel Sandbox:

  • Vercel OIDC tokens through VERCEL_OIDC_TOKEN. The SDK uses VERCEL_PROJECT_ID and VERCEL_TEAM_ID when present, and falls back to decoding them from the token payload when possible.
  • Access tokens through token=..., or through VERCEL_TOKEN together with VERCEL_PROJECT_ID and VERCEL_TEAM_ID.

For setup details, see Authentication.


Was this helpful?