Official Python SDK and CLI for InstaVM APIs
Reason this release was yanked:
Published with incorrect version; intended release is 0.22.0
Project description
InstaVM Python SDK + CLI
Official Python SDK and installed CLI for InstaVM. Use it to manage VMs, snapshots, shares, volumes, desktops, account settings, and code execution from Python or your shell.
Installation
pip install instavm
Requirements: Python 3.10+
Table of Contents
- CLI
- Library Quick Start
- Code Execution
- Sessions & Sandboxes
- VMs & Snapshots
- Volumes
- Networking
- Browser Automation
- Computer Use
- PTY (Interactive Terminals)
- OpenAI Agents SDK
- Secrets Vault
- Time Travel / Time Machine
- Platform APIs
- Error Handling
- Development & Testing
- Changelog
CLI
pip install instavm installs the instavm command in the active Python environment.
instavm --help
python -m instavm.cli --help
The CLI stores defaults in ~/.instavm/config.json, checks INSTAVM_API_KEY when no key is stored, and also respects INSTAVM_BASE_URL and INSTAVM_SSH_HOST. instavm login is the easiest way to populate the config — see Auth & Config below.
Multi-step commands (login, vm create, cookbook deploy, …) render a Clack-inspired progress rail with a single spinner per step. The UI auto-degrades to plain text when output is not a TTY, when NO_COLOR is set, when TERM=dumb, or when --json is passed — --json payloads on stdout are unchanged.
Auth & Config
The fastest way to authenticate is the browser-based login (recommended for laptops/workstations):
instavm login
This opens your browser, asks you to confirm the pairing on the dashboard,
and writes a labelled API key (CLI – <hostname>) back to your local config.
The key never touches the URL bar — it's exchanged server-to-server over a
local loopback callback using PKCE. To switch accounts, run
instavm auth logout then instavm login again.
For headless environments (CI, servers without a browser) paste an existing key:
instavm auth set-key # prompts for the key (stdin / hidden)
printf '%s' "$INSTAVM_API_KEY" | instavm auth set-key
instavm auth status
Pointing the CLI at a non-production environment
The CLI honors INSTAVM_BASE_URL (and the --base-url flag). To run the same
flow against staging — useful for verifying a release before it reaches
production:
export INSTAVM_BASE_URL=https://api.staging.instavm.io
instavm login # browser will open the staging dashboard
The login flow always opens the dashboard URL returned by the API for the
configured base URL, so a single INSTAVM_BASE_URL switches both the API and
the browser side of the handshake.
If your dashboard's API key gets revoked from the web UI, the next CLI call
will print a hint to re-run instavm login.
Common Commands
instavm whoami
instavm ls
instavm ls -a
instavm ls --watch
instavm create --type computer-use --memory 4096
instavm connect vm_123
instavm pty vm_123 # interactive PTY (like docker exec -it)
instavm pty vm_123 /bin/zsh # custom shell program
instavm deploy
instavm deploy --plan
instavm egress get --vm vm_123
instavm exec --cmd "print('hello from CLI')" --language python
instavm exec ./script.py
instavm browser read https://example.com
instavm browser screenshot https://example.com --out page.png
instavm browser session create
instavm browser navigate https://example.com --session $SID
instavm browser click "button#submit" --session $SID
instavm browser type "input[name=q]" "hello world" --session $SID
instavm browser fill "input[name=email]" "user@example.com" --session $SID
instavm browser scroll --session $SID --y 500
instavm browser extract --session $SID --selector "a"
instavm browser session close $SID
instavm snapshot ls
instavm snapshot get <snapshot_id> --watch
instavm volume ls
instavm volume files upload <volume_id> ./README.md --path docs/README.md
instavm share create vm_123 3000 --public
instavm share set-private <share_id>
instavm ssh-key list
instavm desktop viewer <session_id>
instavm doc
instavm billing
instavm ls shows active VMs only. Use -a or --all to include terminated VM records. On ANSI terminals the human-readable list uses colored status badges, and both instavm ls and instavm snapshot get support --watch for periodic refreshes.
Cookbooks
instavm cookbook pulls curated starter apps from the public instavm/cookbooks catalog, creates a VM, starts the service, creates the share, and returns the public URL.
instavm cookbook list
instavm cookbook info neon-city-webgl
instavm cookbook deploy neon-city-webgl
instavm cookbook deploy hello-fastapi
The CLI syncs the cookbook repo into ~/.instavm/cookbooks/ (which requires Git — only when you run instavm cookbook … without INSTAVM_COOKBOOKS_DIR), checks for ssh, scp, and tar before every deploy, prompts for any required secrets, and auto-registers a local public SSH key if your account does not already have one.
Deploy
instavm deploy tries to deploy the app in the current directory without asking you to create an instavm.yaml first. It detects a simple Node.js or Python web app, creates a VM, uploads the project, starts the service, and gives you a share URL.
instavm deploy
instavm deploy --plan
instavm deploy ./path/to/app
instavm deploy -f instavm.web.yaml
--plan shows the detected runtime, install command, start command, port, and secrets without creating a VM.
When a project has an instavm.yaml, instavm deploy uses it instead of zero-config detection. Manifest schema_version: 2 is accepted alongside v1 and adds a top-level lifecycle kind:
schema_version: 2
kind: service # service | cron | job
deploy:
kind: upload_and_run # deploy.source is also accepted as an alias
kind: service deploys through the existing VM/share flow. kind: cron and kind: job are parsed and shown by --plan; deployment fails with a clear unsupported-control-plane message until the scheduler/job backend APIs are available in this CLI. Use -f/--file to target manifests such as instavm.web.yaml or instavm.cron.yaml.
instavm deploy is experimental right now. The zero-config path is working best for straightforward Node.js and Python apps. Some runtimes and projects still need follow-up fixes or backend support.
YAML v2 Guide
The supported YAML v2 manifest guide lives in docs/instavm_yaml_v2.md.
It explains the schema, the major blocks, and the quickest way to use a v2 manifest with instavm deploy.
Command Reference
auth:set-key,status,logoutwhoami: show account details and SSH keysls/list: show active VMs by default; use-aor--allfor all VM recordscookbook:list,info,deployfor curated starter apps frominstavm/cookbooksdeploy: experimental zero-config deploy for the current app directoryegress:get,setfor session and VM network egress policyexec: run inline code or a local file, plusresultfor async task lookuppty: open an interactive PTY inside a VM (likedocker exec -it)browser:read,screenshot,navigate,click,type,fill,scroll,wait,extract,sessioncreate/new,rm/delete,clone,connect: core VM workflowssnapshot:ls,create,build,get,rmdesktop:status,start,stop,viewervolume:ls,get,create,update,rm,checkpoint,filesshare:create,set-public,set-private,revokessh-key:list,add,removevm/vms: VM-scoped operations mirroringclient.vms.*—update(live--memory-mb,--snapshot-on-terminate,--snapshot-name)desktop: now also hostsrecordings(ls,get,download,rm) for computer-use session video capturesbilling: defaults to printing the Stripe portal URL; subcommands cover the full credit/usage surface —portal,status,allocation,usage(daily $ breakdown),usage-history(per-event rows),check,rates,trends,forecastdoc/docs: documentation links
All leaf commands support --json. Share visibility updates use share_id, which matches the public API.
Library Quick Start
import os
from instavm import InstaVM
client = InstaVM(
api_key=os.environ.get("INSTAVM_API_KEY"),
auto_start_session=False,
)
me = client.get_current_user()
vms = client.vms.list()
print(me["email"])
print(len(vms))
Code Execution
Sessions & Configuration
from instavm import InstaVM
client = InstaVM(
api_key="your_api_key",
cpu_count=2,
memory_mb=1024,
env={"APP_ENV": "dev"},
metadata={"team": "platform"},
)
result = client.execute("print('session id:', 'ok')")
print(result)
print(client.session_id)
File Operations
client = InstaVM(api_key="your_api_key")
client.upload_file("local_script.py", "/app/local_script.py")
client.execute("python /app/local_script.py", language="bash")
client.download_file("output.json", local_path="./output.json")
Async Execution
client = InstaVM(api_key="your_api_key")
task = client.execute_async("sleep 5 && echo 'done'", language="bash")
result = client.get_task_result(task["task_id"], poll_interval=2, timeout=60)
print(result)
Sessions & Sandboxes
client = InstaVM(api_key="your_api_key")
# Get the publicly-reachable app URL (optionally for a specific port)
app_url = client.get_session_app_url(port=8080)
print(app_url.get("app_url"))
# List sandbox records with optional metadata filter and limit
sandboxes = client.list_sandboxes(metadata={"env": "production"}, limit=50)
print(len(sandboxes))
VMs & Snapshots
client = InstaVM(api_key="your_api_key")
# Create a basic VM
vm = client.vms.create(wait=True, metadata={"purpose": "dev"})
# Create a VM with pre-attached volumes
vm_with_vols = client.vms.create(
wait=True,
volumes=[{"volume_id": "vol_abc", "mount_path": "/data", "mode": "rw"}],
)
# List VMs
vms = client.vms.list() # GET /v1/vms (running)
all_records = client.vms.list_all_records() # GET /v1/vms/ (all records)
# Snapshot a running VM
snap_from_vm = client.vms.snapshot(vm_id=vm["vm_id"], wait=True, name="dev-base")
# Build a snapshot from an OCI image
snap_from_oci = client.snapshots.create(
oci_image="docker.io/library/python:3.11-slim",
name="python-3-11-dev",
vcpu_count=2,
memory_mb=1024,
snapshot_type="user",
build_args={
"git_clone_url": "https://github.com/example/repo.git",
"git_clone_branch": "main",
"envs": {"PIP_INDEX_URL": "https://pypi.org/simple"},
},
)
user_snaps = client.snapshots.list(snapshot_type="user")
Volumes
Volume CRUD & Files
client = InstaVM(api_key="your_api_key")
# Create
volume = client.volumes.create(name="project-data", quota_bytes=10 * 1024 * 1024 * 1024)
volume_id = volume["id"]
# Read / Update
client.volumes.list(refresh_usage=True)
client.volumes.get(volume_id, refresh_usage=True)
client.volumes.update(volume_id, name="project-data-v2", quota_bytes=20 * 1024 * 1024 * 1024)
# File operations
client.volumes.upload_file(volume_id, file_path="./README.md", path="docs/README.md", overwrite=True)
files = client.volumes.list_files(volume_id, prefix="docs/", recursive=True, limit=1000)
download = client.volumes.download_file(volume_id, path="docs/README.md")
client.volumes.delete_file(volume_id, path="docs/README.md")
# Checkpoints
checkpoint = client.volumes.create_checkpoint(volume_id, name="pre-release")
client.volumes.list_checkpoints(volume_id)
client.volumes.delete_checkpoint(volume_id, checkpoint["id"])
# Cleanup
client.volumes.delete(volume_id)
VM Volume Attachments
vm = client.vms.create(wait=True)
vm_id = vm["vm_id"]
client.vms.mount_volume(vm_id, volume_id, mount_path="/data", mode="rw", wait=True)
client.vms.list_volumes(vm_id)
client.vms.unmount_volume(vm_id, volume_id, mount_path="/data", wait=True)
Networking
Egress, Shares, SSH
client = InstaVM(api_key="your_api_key")
# Egress policy
policy = client.set_session_egress(
allow_package_managers=True,
allow_http=False,
allow_https=True,
allowed_domains=["pypi.org", "files.pythonhosted.org"],
)
# Public/private share links
share = client.shares.create(port=3000, is_public=False)
client.shares.update(share_id=share["share_id"], is_public=True)
# SSH key registration
key = client.add_ssh_key("ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... user@host")
Browser Automation
Basic Browser Flow
client = InstaVM(api_key="your_api_key")
session = client.browser.create_session(viewport_width=1366, viewport_height=768)
session.navigate("https://example.com")
links = session.extract_elements("a", ["text", "href"])
shot_b64 = session.screenshot(full_page=True)
session.close()
Interactions: Click, Type, Fill, Scroll
All interaction methods use CSS selectors to target elements (not pixel coordinates):
client = InstaVM(api_key="your_api_key")
with client.browser.create_session() as session:
session.navigate("https://www.google.com")
# Type into an input field (keystroke-by-keystroke with configurable delay)
session.type("textarea[name=q]", "InstaVM cloud VMs", delay=50)
# Fill a form field (clears existing value first, sets value instantly)
session.fill("input[name=email]", "user@example.com")
# Click an element (supports force-click for obscured elements)
session.click("button[type=submit]", force=False)
# Scroll the page or a specific element
session.scroll(y=500)
session.scroll(selector="#results")
# Wait for a condition before continuing
session.wait_for("visible", selector="#results", timeout=5000)
# Extract DOM elements with specific attributes
elements = session.extract_elements("a.result-link", ["href", "text"])
for el in elements:
print(el["href"], el["text"])
# Take a screenshot
session.screenshot(full_page=True)
Content Extraction
LLM-friendly extraction with optional interactive-element and anchor discovery:
client = InstaVM(api_key="your_api_key")
content = client.browser.extract_content(
url="https://example.com/docs",
include_interactive=True,
include_anchors=True,
max_anchors=30,
)
print(content["readable_content"].get("title"))
for anchor in (content.get("content_anchors") or [])[:5]:
print(anchor.get("text"), anchor.get("selector"))
Computer Use
Control a full desktop environment inside a VM session:
client = InstaVM(api_key="your_api_key")
session_id = client.session_id
# Viewer URL and state
viewer = client.computer_use.viewer_url(session_id)
state = client.computer_use.get(session_id, "/state")
# Proxy methods (GET, POST, HEAD)
head_resp = client.computer_use.head(session_id, "/state")
# VNC websockify URL for remote desktop streaming
vnc = client.computer_use.vnc_websockify(session_id)
PTY (Interactive Terminals)
Create and manage interactive pseudo-terminal sessions inside VMs. Used by the OpenAI Agents SDK's Shell capability for write_stdin support.
pip install instavm[pty] # adds websockets dependency
import asyncio
from instavm import InstaVM
client = InstaVM(api_key="your_api_key")
session_id = client.session_id
# Create a PTY session
pty_info = client.pty.create(session_id, cols=120, rows=40)
pty_id = pty_info["session_id"]
# List / get / resize / kill PTY sessions
sessions = client.pty.list(session_id)
info = client.pty.get(session_id, pty_id)
client.pty.resize(session_id, pty_id, cols=200, rows=50)
client.pty.kill(session_id, pty_id)
# WebSocket URL for interactive I/O (use with websockets library)
ws_url = client.pty.ws_url(session_id, pty_id)
WebSocket Protocol
Connect to ws_url for bidirectional PTY I/O:
- Client → Server: Binary frames = stdin, Text frames = JSON control (
{"type": "resize", "cols": N, "rows": N}) - Server → Client: Binary frames = stdout/stderr, Text frame = exit notification (
{"type": "exit", "exit_code": N})
OpenAI Agents SDK Integration
With PTY support enabled, the Shell capability's write_stdin tool works automatically:
from agents.sandbox import SandboxAgent
agent = SandboxAgent(
name="Developer",
model="gpt-5.4",
instructions="Run interactive commands and inspect output.",
)
# Shell tool's write_stdin is available when supports_pty() returns True
OpenAI Agents SDK: Sandbox Provider
To use InstaVM as a sandbox backend for the OpenAI Agents SDK, install the agents extra:
pip install instavm[agents]
This pulls in openai-agents>=0.14.1,<0.15 and registers InstaVM as a sandbox provider automatically. Each agent runs in its own cloud VM with filesystem, shell, networking, and snapshot support.
import asyncio, os
from agents import Runner, RunConfig
from agents.sandbox import SandboxAgent, SandboxRunConfig
from instavm.integrations.openai_agents import (
InstaVMSandboxClient,
InstaVMSandboxClientOptions,
)
agent = SandboxAgent(
name="Analyst",
model="gpt-5.4",
instructions="Analyze workspace files and answer concisely.",
)
async def main():
client = InstaVMSandboxClient(api_key=os.environ["INSTAVM_API_KEY"])
result = await Runner.run(
agent,
"What OS is this sandbox running?",
run_config=RunConfig(
sandbox=SandboxRunConfig(
client=client,
options=InstaVMSandboxClientOptions(memory_mb=1024),
),
),
)
print(result.final_output)
asyncio.run(main())
Features: streaming, resume & snapshots, persistent volumes, egress control, cloud bucket mounts, exposed ports, and more.
📖 Full documentation · 📂 Examples
Secrets Vault
Store API keys (OpenAI, Anthropic, Stripe, GitHub, …) in InstaVM once, then bind
them to upstream hosts. Real values never leave the server: the in-VM transparent
MITM proxy substitutes them at TLS-write time, so any HTTPS SDK in any language
works unchanged. Code inside the VM only ever sees a placeholder name like
OPENAI_KEY; the wire carries the real token.
A vault is a unit of secret + access policy. Inside a vault:
- a credential is a named secret (
OPENAI_KEY,STRIPE_SECRET, …). - a service is a binding that says "when this VM hits
api.openai.com, inject credentialOPENAI_KEYas a Bearer token". Use--template openai|anthropic|…for the built-in catalog or--hostfor a custom upstream.
Vault CLI (instavm vault …, aliases secrets, secret)
| Subcommand | What it does |
|---|---|
vault list / vault ls |
List vaults you own. |
vault create <name> [--description …] |
Create a new vault. |
vault get <vault-id> |
Vault metadata. |
vault update <vault-id> [--name …] [--description …] |
Rename / re-describe. |
vault delete <vault-id> / vault rm |
Delete vault and all credentials. Asks for confirmation unless --yes. |
vault discover <vault-id> |
Safe summary (credential names + bound services, no values). |
vault catalog |
List built-in service templates (openai, anthropic, stripe, github, …). |
vault logs <vault-id> [--limit N] |
Recent request-log entries (which credential was injected, when, by which VM). |
vault setup [PATH] |
Interactive bootstrap for a cookbook. Reads instavm.yaml, scans org vaults, and walks you through creating + binding any missing host. Same flow instavm deploy triggers automatically when vault.required: true and nothing matches. Runs idempotently — re-running on a fully-covered cookbook is a no-op (already_covered). |
Credentials (instavm vault secret …)
| Subcommand | What it does |
|---|---|
vault secret list <vault-id> / ls |
List credential names + ids in a vault. Never returns values. |
vault secret set <vault-id> <name> |
Add a credential. Value comes from --value-file, stdin, an interactive getpass prompt (default — never echoed, never recorded in shell history), or --value (last-resort; visible in shell history). --type api_key (default), --description …. |
vault secret rotate <vault-id> <credential-id> |
Replace a value while keeping the same name and id. Same value-source rules as set. |
vault secret delete <vault-id> <credential-id> / rm |
Delete a credential. |
# CI-friendly: stream a value from a process substitution, no shell history.
INSTAVM_API_KEY=$INSTAVM_API_KEY instavm vault secret set $VAULT_ID OPENAI_KEY \
--value-file <(printf '%s' "$OPENAI_KEY")
Service bindings (instavm vault service …)
| Subcommand | What it does |
|---|---|
vault service list <vault-id> / ls |
List bindings on a vault. |
vault service add <vault-id> --template openai | --host api.example.com [--auth-type bearer|header|url-path] [--header X-API-Key] [--placeholder TOKEN] [--credential OPENAI_KEY] [--description …] [--disabled] |
Bind a credential to an upstream host. --template and --host are mutually exclusive — exactly one is required. Use --auth-type url-path for upstreams that embed the token in the URL path (e.g. Telegram's /bot<TOKEN>/...); --placeholder names the sentinel the in-VM caller will use (defaults to --credential). |
vault service remove <vault-id> <service-id> / rm |
Remove a binding. |
SDK (client.list_vaults, client.create_vault, …)
import os
from instavm import InstaVM
with InstaVM(api_key="…") as client:
vault = client.create_vault("prod-keys", description="Shared agent secrets")
# Add a credential — value is sent over TLS to the platform, then stored encrypted.
cred = client.add_vault_credential(
vault["id"], name="OPENAI_KEY", value=os.environ["OPENAI_API_KEY"],
credential_type="api_key", description="Team OpenAI key",
)
# Bind it to api.openai.com using the built-in template.
client.add_vault_services_from_templates(vault["id"], template_ids=["openai"])
# Or bind a custom host:
client.add_vault_service(
vault["id"], host="api.example.com",
auth_config={"type": "bearer", "token": "OPENAI_KEY"},
)
# Path-based auth (e.g. Telegram embeds the bot token in /bot<TOKEN>/…).
# The placeholder is the sentinel the in-VM code will embed; the MITM
# proxy swaps it for the real value before the request leaves the VM.
client.add_vault_credential(
vault["id"], name="TELEGRAM_BOT_TOKEN", value=os.environ["TELEGRAM_BOT_TOKEN"],
)
client.add_vault_services_from_templates(vault["id"], template_ids=["telegram"])
# …or the same thing manually:
client.add_vault_service(
vault["id"], host="api.telegram.org",
auth_config={
"type": "url-path",
"token": "TELEGRAM_BOT_TOKEN",
"placeholder": "TELEGRAM_BOT_TOKEN",
},
)
# Audit / inspect — values are never returned, only names + metadata.
client.discover_vault(vault["id"])
client.list_vault_credentials(vault["id"])
client.list_vault_services(vault["id"])
client.get_vault_request_logs(vault["id"], limit=50)
# Lifecycle
client.rotate_vault_credential(vault["id"], cred["id"], value=os.environ["OPENAI_API_KEY_NEW"])
client.delete_vault_credential(vault["id"], cred["id"])
client.delete_vault(vault["id"])
Once a VM is launched against the vault, code inside the sandbox can do this and
the real OPENAI_API_KEY is substituted on the wire — even though the VM's
environment only has the placeholder:
# Inside the VM:
import os
from openai import OpenAI
# OPENAI_API_KEY is a placeholder string; the MITM swaps it with the real value.
client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])
client.chat.completions.create(model="gpt-4o-mini", messages=[…])
The same pattern works for upstreams that carry the credential in the URL path instead of a header. The bot token is never present in the VM environment; the MITM proxy injects it on the wire:
# Inside the VM (Telegram example):
import os, requests
# TELEGRAM_BOT_TOKEN is just the literal string "TELEGRAM_BOT_TOKEN" inside the VM.
# The MITM proxy swaps it for the real value before the request leaves the VM.
token = os.environ.get("TELEGRAM_BOT_TOKEN", "TELEGRAM_BOT_TOKEN")
requests.get(f"https://api.telegram.org/bot{token}/getMe").raise_for_status()
Supported injection modes
auth_type |
Where the credential lands on the wire | Typical templates |
|---|---|---|
bearer |
Authorization: Bearer <secret> header |
openai, openrouter |
api-key (a.k.a. header) |
Custom header (x-api-key, Authorization, …) with optional prefix |
anthropic, gemini, stripe |
basic |
Authorization: Basic base64(user:pass) header |
jira |
custom |
One or more custom headers built from {{ CRED }} placeholders |
enterprise APIs |
url-path |
Substring substitution inside the request URL path | telegram |
passthrough |
Allowlist-only; request passes through unmodified | audit-only hosts |
For the full list of verified-compatible HTTPS clients (Python, Node, Go, Java, Ruby, PHP, Perl, curl, wget, …), see VAULT_SDK_COMPATIBILITY.
Time Travel / Time Machine
Every InstaVM session can be recorded as a tape — an append-only log of state changes inside the VM. A tape captures:
| Lane | What it records | Source |
|---|---|---|
fs_summary |
filesystem step checkpoints (changed paths) | platform |
http_egress |
every outbound HTTP/HTTPS request from the VM | MITM proxy (auto) |
llm_call |
OpenAI / Anthropic / Bedrock / Gemini calls | MITM proxy (auto) |
tool_call_start / tool_call_end |
agent tool invocations | ToolCallRecorder |
vnc_marker |
desktop interaction markers | platform |
agent_log / branch |
freeform agent annotations and fork points | SDK |
Once a tape is recording, the platform's MITM proxy auto-emits http_egress and
llm_call events — no SDK shim required for those. The result is a tape you can
scrub, diff, branch (fork the VM at a past step), or replay LLM calls offline.
CLI subcommands (instavm tape …)
| Subcommand | What it does |
|---|---|
tape ls |
List tapes. Filters: --session, --vm, --status {recording|stopped|failed|expired}, --limit. |
tape start <session-id> |
Start recording. --no-record-fs to skip fs checkpoints. --record-egress-content to capture request/response bodies (when cassette support is enabled). --retention-days N (default 7). |
tape stop <tape-id> |
Stop a recording and finalize totals. |
tape get <tape-id> |
Show tape metadata (status, step count, retention, session/VM). |
tape events <tape-id> |
Stream events. --after-step N for incremental tail, --kind fs_summary|http_egress|llm_call|…, --limit. |
tape lanes <tape-id> |
Per-lane event counts — the data behind a scrubber UI. |
tape play <tape-id> |
ASCII multi-track timeline + tail of recent events. --limit, --tail. |
tape diff <tape-id> --from S1 --to S2 |
Filesystem changed-paths between two steps of the same tape. |
tape branch <tape-id> --at S |
Spawn a new VM/session forked from this tape at step S. --mode live|replay. Prints ssh <vm_id>@<host> for the fork. |
tape export <tape-id> [--output file.json] |
Self-contained JSON bundle (metadata + events) for archival or external playback. |
tape rm <tape-id> |
Delete a tape. |
SDK (client.tapes, client.tape())
from instavm import InstaVM
# InstaVM is itself a context manager; constructor auto-starts a session.
with InstaVM(api_key="…") as client:
# tape with-block: starts on enter, stops + finalizes on exit (even on exceptions)
with client.tape(record_fs=True, retention_days=7) as tape:
client.execute("mkdir -p /data && echo hi > /data/note.md")
client.execute("curl -s https://news.ycombinator.com -o /data/hn.html")
print("recorded:", tape.id)
# Inspect — same names as the CLI. events() returns a list[dict].
client.tapes.list(session_id=…, vm_id=…, status="stopped")
client.tapes.get(tape.id)
for ev in client.tapes.events(tape.id, after_step=0, kind=None, limit=200):
print(ev["kind"], ev.get("step_id"))
client.tapes.lanes(tape.id)
client.tapes.diff(tape.id, from_step=10, to_step=42)
fork = client.tapes.branch(tape.id, at_step=42, mode="live") # → new vm_id
client.tapes.export(tape.id) # full JSON bundle
client.tapes.delete(tape.id)
Recording agent tool calls
ToolCallRecorder is a generic middleware that emits tool_call_start /
tool_call_end events around any agent-loop tool invocation:
from instavm.sandbox_client import ToolCallRecorder
recorder = ToolCallRecorder(client.tapes, tape.id)
with recorder.span("python_exec", payload={"code": "1+1"}) as step:
out = client.execute("python -c 'print(1+1)'")
step.attach({"output": out.get("stdout") if isinstance(out, dict) else out})
Replaying LLM calls offline (cassette)
Once a tape has recorded llm_call events, make_openai_client(tape_id=…) returns a
ready-to-use openai.OpenAI instance whose HTTP transport is backed by the cassette —
no network, no API key, deterministic outputs:
from instavm.cassette_replay import make_openai_client
oai = make_openai_client(tape_id=tape.id) # returns a pre-wired openai.OpenAI client
oai.chat.completions.create(model="gpt-5-nano", messages=[…])
For other vendor SDKs (Anthropic, Bedrock, …) drop down a layer:
import httpx
from instavm.cassette_replay import CassetteReplayClient
transport = CassetteReplayClient(tape_id=tape.id, strict=True).as_httpx_transport()
http = httpx.Client(transport=transport)
# pass `http` into your SDK's http_client= argument
Lookup is keyed by (METHOD, URL, sha256(body)); repeated identical requests are served
in cassette order so polling endpoints replay correctly. Cassette root defaults to
/var/lib/instavm/tapes and is overridable via INSTAVM_TAPES_CASSETTE_ROOT.
📂 Example
Platform APIs
API keys, audit logs, webhooks, recordings, credits, and usage:
client = InstaVM(api_key="your_api_key")
# API Keys
api_key = client.api_keys.create(description="ci key")
# Audit log
audit_page = client.audit.events(limit=25, status="success")
# Webhooks
endpoint = client.webhooks.create_endpoint(
url="https://example.com/instavm/webhook",
event_patterns=["vm.*", "snapshot.*"],
)
deliveries = client.webhooks.list_deliveries(limit=10)
# Computer-use session recordings
recordings = client.recordings.list(status="ready", limit=10)
url = client.recordings.get_download_url(recordings[0]["id"])
client.recordings.download(recordings[0]["id"], "/tmp/session.mp4")
# Credits / usage
allocation = client.credits.allocation()
summary = client.credits.summary(period="current_month")
trends = client.credits.usage_trends(period="30d", granularity="daily")
# Day-wise spend breakdown (cost / CPU hours / RAM GiB-hours)
breakdown = client.get_usage_breakdown(days=30)
The same surface is available from the CLI. Money/usage commands live under billing; recordings live under desktop (since they're captures of computer-use desktop sessions); VM-scoped operations live under vm/vms:
# Recordings — under `desktop` because they're desktop session captures
instavm desktop recordings ls --status ready
instavm desktop recordings download rec_123 -o ~/Downloads/session.mp4
# Money / usage — all under `billing`
instavm billing # default: print Stripe portal URL
instavm billing status # current-period credit summary
instavm billing usage --days 14 # day-wise $ / CPU-h / RAM GiB-h
instavm billing usage-history --period last_30_days # per-event credit history
instavm billing check --usage-type browser_automation --required-credits 5
instavm billing trends --period 30d --granularity daily
# VM-scoped — mirrors client.vms.*
instavm vm update vm_42 --memory-mb 1024 --snapshot-on-terminate --snapshot-name auto-vm_42
Error Handling
All SDK errors extend a typed hierarchy for precise except handling:
from instavm import (
InstaVM,
AuthenticationError,
ExecutionError,
NetworkError,
RateLimitError,
SessionError,
)
client = InstaVM(api_key="your_api_key")
try:
client.execute("raise Exception('boom')")
except AuthenticationError:
print("Invalid API key")
except RateLimitError:
print("Rate limited")
except SessionError as exc:
print(f"Session issue: {exc}")
except ExecutionError as exc:
print(f"Execution failed: {exc}")
except NetworkError as exc:
print(f"Network issue: {exc}")
Development & Testing
pip install -e . # Install for development
python3 -m pytest tests/test_api_client.py -v # Unit tests
Further Reading
- Python SDK Overview
- VM Management
- Snapshots
- Egress and Networking
- Platform APIs
- Browser Automation
- Error Handling
Changelog
Current package version: 0.25.0
0.25.0
Added a crisp Clack-inspired progress rail for interactive CLI commands. TTY sessions now get grouped ┌ / │ / └ progress with a restrained spinner and single accent color, powered by rich, while --json, NO_COLOR, TERM=dumb, and non-TTY output preserve the existing machine-readable/plain contracts. VM create/delete/clone, snapshot create/delete, desktop start/stop, and VM update waits now use the rail instead of blocking silently. Watch-mode refreshes use rich.console.Console.clear() on the fancy path, and progress headers only show command names rather than positional operands such as local paths or VM IDs.
0.24.0
Added a published YAML v2 guide in docs/instavm_yaml_v2.md, linked it from the README, and added v1 regression coverage so the v2 merge does not break existing manifests.
0.23.0
Cookbook deploy with secrets is now a one-command flow. When a cookbook declares vault.required: true and the org has no matching vault, instavm deploy no longer fails fast — it walks the user through creating the vault, storing the secret, and binding the upstream host on the spot. The previous five-command setup recipe still works (and is still printed by --no-setup-vault for CI), but the default UX is now "edit instavm.yaml, run instavm deploy, paste your API key once."
Interactive vault bootstrap on first deploy
instavm deployself-bootstraps a vault when needed. New behavior triggered when the manifest declaresvault.required: true, the caller didn't pass--vault VAULT_ID, no org vault covers the declared hosts, and stdin is a TTY. The CLI offersBootstrap a vault now and walk through N secret(s)? [Y/n], then for each missing host: prompts for the credential value viagetpass, stores it in a vault, and binds the service. After the bootstrap, deploy continues normally with the new vault attached.- Smart binding plan: uses the server's prebuilt template catalog (
GET /v1/vaults/catalog→openai,anthropic,stripe,github,linear,notion,groq,perplexity,elevenlabs,cloudflare,datadog,sentry,slack,vercel,supabase,jira,hubspot,twilio,pagerduty,postmark,resend,sendgrid) when the host is known, falls back to a built-in mapping for the major LLM hosts (OpenAI / Anthropic / OpenRouter / Google Gemini), and synthesizes a<HOST_LABEL>_API_KEYbearer-auth binding for unknown hosts. - Reuses existing vaults. When discovery already covers SOME of the declared hosts (e.g. you have
api.openai.combound and a new cookbook needsapi.anthropic.com), the bootstrap extends the existing vault rather than creating a new one — re-running setup is idempotent. - Skips re-prompting when the credential already exists. If the vault has the credential under the catalog key (e.g.
OPENAI_API_KEY), the bootstrap silently reuses it and only adds the missing service binding. Re-runninginstavm deployon a fully-covered cookbook is a no-op. - Combines catalog binds into one HTTP call. Multiple catalog hosts get bound via a single
POST /v1/vaults/{id}/services:from_templaterequest instead of one round-trip per host. - Gracefully degrades. When stdin isn't a TTY (CI, piped input) or
--no-setup-vaultis passed, the deploy still fails fast with a copy-pasteable recipe; the bootstrap never blocks unattended runs.
instavm vault setup [PATH] — same flow, no VM
- Standalone command that runs the same bootstrap without provisioning a VM. Reads
instavm.yaml, runs vault discovery, and either reportsalready_covered(exit 0) or walks you through creating the missing bindings. Useful for pre-provisioning the org vault once before non-interactiveinstavm deploycalls. instavm vault setup ./openai-agents-python-research --jsonexits 0 with{"status": "ok", "vault_ids": [...]}after success.
Library-callable helper
-
from instavm._cookbook import bootstrap_vault_for_manifestexposes the helper for tools that wrap the cookbook deploy pipeline:bootstrap_vault_for_manifest( client, manifest, missing_hosts=("api.openai.com",), matched_pairs=(), prompt_secret=lambda label: ..., prompt_confirm=lambda text, default: True, )
-
_run_deploy(...)anddeploy_cookbook(...)accept newallow_vault_bootstrap(defaults toTruein the CLI,Falsefor library calls),vault_bootstrap_prompt_secret, andvault_bootstrap_prompt_confirmkwargs so callers can plug in their own UI. -
instavm deploy ./dirno longer requires Git. Preflight only checksssh,scp, andtar(the SSH upload bundle path). Git is verified lazily insidesync_cookbook_repo()wheninstavm cookbook list|info|deploy <slug>needs to clone/updateinstavm/cookbooks; if Git is missing there, the error explains how to install it or setINSTAVM_COOKBOOKS_DIR.
CLI flags
instavm deploy --no-setup-vaultandinstavm cookbook deploy --no-setup-vault— opt out of the interactive bootstrap; deploy fails fast with the manual recipe instead.- All previous vault flags (
--vault VAULT_ID,--no-vault) still work and take precedence over the bootstrap prompt.
0.22.0
This release rolls up everything since 0.21.0 (the last build published to PyPI). Headlines: a Secrets Vault that lets HTTPS clients inside the VM call third-party APIs without ever holding a real key, time-travel recording/replay for agent sessions, full coverage of the recordings + credits + usage backend surfaces, and instavm deploy is now vault-aware so cookbooks can be wired to org credentials with a single line in instavm.yaml.
Secrets Vault
-
Store API keys once, inject transparently into VMs. New
instavm vault(aliasessecrets,secret) command tree — create vaults, add credentials, bind upstream services, audit request logs. Real secret values never leave the server: the in-VM transparent MITM substitutes them at TLS-write time, so any HTTPS SDK in any language works unchanged (verified: Pythonopenai/httpx/requests, Nodefetch, Gonet/http, JavaHttpClient, Ruby, PHP, Perl, curl, wget — see VAULT_SDK_COMPATIBILITY).instavm vault list|create|get|update|delete|discover|catalog|logsinstavm vault secret list|set|rotate|delete— values come from--value-file, stdin, an interactivegetpassprompt (never echoed, never recorded in shell history), or--value(last-resort; visible in shell history).instavm vault service list|add|remove— bind a credential to a host with--template openai|anthropic|stripe|github|…from the built-in catalog or with a custom--host(mutually exclusive with--template).- SDK:
client.list_vaults,create_vault,add_vault_credential,add_vault_service,add_vault_services_from_templates,get_vault_request_logs, etc. - Designed for use in CI:
INSTAVM_API_KEY=$INSTAVM_API_KEY instavm vault secret set $VID OPENAI_KEY --value-file <(echo "$OPENAI_KEY")— works the same way asfly secrets setorgh secret set.
-
instavm deployauto-binds org vaults to the new VM. Cookbooks declare the upstream hosts they need credentials for via a newvault:block ininstavm.yaml; the CLI scans the caller's org vaults at deploy time, picks any vault that has a service binding for at least one declared host, and passes the matchingvault_idsthrough toPOST /v1/vms. The in-VM transparent MITM then activates for those hosts so the cookbook reachesapi.openai.com(etc.) without ever holding a real key. Closes the gap that previously made vault-aware cookbooks 401 even when an org vault existed, becauseinstavm deployhad no way to attach it.- Manifest schema (additive, optional):
vault: required: true # fail-fast deploy if no matching vault is found hosts: # services this cookbook expects MITM to inject - api.openai.com
Adding avault:block does not break older CLIs — the field is ignored when absent. - CLI flags on both
instavm deployandinstavm cookbook deploy:--vault VAULT_ID(repeatable) — attach specific vaults explicitly, bypassing auto-discovery.--no-vault— opt out of auto-discovery even when the manifest declares hosts.--no-setup-vault— disable the interactive bootstrap (see below) forvault.required: truecookbooks; deploy fails fast with a setup recipe instead of prompting.
- SDK pass-through:
_cookbook.create_vm()and_run_deploy()accept avault_ids=kwarg that lands in the VM-create payload.client.vms.create(**payload)already passed unknown keys through, so any caller can doclient.vms.create(vault_ids=["vlt_…"])directly.
- Manifest schema (additive, optional):
-
instavm deploywalks you through vault setup the first time. When a cookbook declaresvault.required: trueand no matching org vault exists, the CLI no longer fails fast — it offers to create + populate the vault on the spot. The new flow is "editinstavm.yaml, runinstavm deploy, paste your API key once" instead of the old five-command setup.- What happens. The CLI scans the org for a matching vault (existing behavior). For any host that's still uncovered, it asks "Bootstrap a vault now and walk through N secret(s)? [Y/n]". On
y, it: (1) reuses an existing vault if at least one declared host is already covered by it, otherwise creates a new vault named<slug>-vault; (2) prompts viagetpassfor each missing credential value (never echoed, never recorded); (3) stores the credentials and binds the services using the server's prebuilt template catalog (openai,anthropic,openrouter,gemini,stripe,github,…) when available, otherwise synthesizes a sensible bearer-auth binding; (4) re-runs discovery to confirm full coverage and proceeds with the deploy. - Standalone command.
instavm vault setup [PATH]runs the same flow without provisioning a VM — useful for pre-provisioning the org vault once before non-interactiveinstavm deploycalls (e.g. in CI). Reportsalready_covered(exit 0) when the vault is already wired up. - Smart credential-key naming. Hosts in the server catalog use the catalog's
credential_key(e.g.OPENAI_API_KEY,ANTHROPIC_API_KEY); unknown hosts get<HOST_LABEL>_API_KEYderived from the host (e.g.api.acme-internal.io→ACME_INTERNAL_API_KEY). When extending an existing vault that already has the credential, the CLI skips the secret prompt and only adds the missing service binding — re-running setup is idempotent. - Non-interactive contexts. When stdin isn't a TTY (CI, piped input) or
--no-setup-vaultis passed, the deploy still fails fast with the same copy-pasteable recipe as before, so the bootstrap never blocks unattended runs. - Library callers.
bootstrap_vault_for_manifest(client, manifest, missing_hosts=…, prompt_secret=…, prompt_confirm=…)exposes the helper; pass non-interactive callbacks for use inside other tools.
- What happens. The CLI scans the org for a matching vault (existing behavior). For any host that's still uncovered, it asks "Bootstrap a vault now and walk through N secret(s)? [Y/n]". On
Time Travel / Time Machine
instavm tapeCLI andclient.tapesSDK for recording, replaying, branching, and exporting agent sessions. Includes lanes (tool_call,fs_summary,http_egress,llm_call,vnc,branch), multi-track ASCII timeline rendering,ToolCallRecordermiddleware for any agent loop, and an offlineCassetteReplayClient(drops intohttpx.MockTransportso any vendor SDK replays recorded LLM calls without a network round-trip).
Recordings, credits, usage breakdown, VM update
- Four backend HTTP surfaces that previously had no SDK or CLI binding, now wrapped. SDK gets two new managers (
client.recordings,client.credits) plusclient.get_usage_breakdown(...); the existingclient.vms.update()gets a CLI counterpart. Endpoints mapped:GET/DELETE /v1/recordings*,GET /v1/credits/{allocation,usage,summary,check,rates,usage/trends,usage/forecasting},GET /v1/users/me/usage-breakdown,PATCH /v1/vms/{vm_id}. - CLI homes (consolidated to keep the top-level uncluttered):
instavm desktop recordings ls|get|download|rm— recordings live underdesktopbecause they're captures of computer-use desktop sessions.downloadresolves the short-lived S3 presigned URL via a no-redirect probe ofGET /v1/recordings/{id}/download; pass-o FILEto stream the bytes locally without sending the InstaVM API key to S3.instavm billing— was a leaf; promoted to a namespace.instavm billingwith no subcommand still prints the Stripe portal URL (backwards compatible). New subcommands:portal,status(current credit summary),allocation,usage(day-wise $ breakdown),usage-history(per-event rows),check,rates,trends,forecast.instavm vm update <vm_id>(aliasinstavm vms update …) — newvm/vmsnamespace mirrorsclient.vms.*. Supports--memory-mb(live memory rebudget),--snapshot-on-terminate/--no-snapshot-on-terminate(mutex group),--snapshot-name. Existing top-levelcreate/rm/clone/connect/shellkeep their shorthand spelling.
0.21.0
instavm login(browser PKCE). Loopback OAuth/PKCE flow opens the dashboard, completes auth in the browser, and writes credentials to~/.instavm/config.json.instavm auth logoutclears them.AuthenticationErrornow hints to re-runinstavm login.
0.19.1
- Fix:
browser navigatenow shows page title. The CLI was readingtitlefrom the wrong level of the API response, always showing-. Now correctly extracts title from the nesteddatafield. - Fix:
egress setnow shows the new policy state. Previously displayed the old state (allallowed) because the backend only returns{"status": "ok"}. The CLI now fetches the updated state after applying.
0.19.0
- Full browser interaction CLI.
instavm browsernow exposesnavigate,click,type,fill,scroll,wait, andextractsubcommands — all backed by Playwright via CSS selectors. Previously these were SDK-only; now they're available from the terminal. - Browser session management CLI.
instavm browser session create|close|lsfor managing persistent browser sessions across multiple interactions. - PTY CLI.
instavm pty <vm_id>opens an interactive terminal inside a VM, likedocker exec -it. Supports custom shell programs and automatic window resize. - ForbiddenError handling. The SDK now catches 403 responses as
ForbiddenErrorinstead of retrying indefinitely, with proper error messages for tier limit violations.
0.18.0
- OpenAI Agents SDK sandbox provider.
pip install instavm[agents]adds InstaVM as a backend for Sandbox Agents with auto-registration, no SDK patches needed. - PTY (interactive terminal) support.
pip install instavm[pty]enablesPtyManagerfor creating, resizing, and killing interactive terminal sessions over WebSocket. The OpenAI Agents SDK'sShellcapability (write_stdin) works automatically when PTY is available. InstaVMSandboxClient/InstaVMSandboxSessionimplementing the full sandbox session contract (exec, read, write, persist, hydrate, resume, exposed ports).InstaVMSandboxClientOptionswith VM sizing, snapshots, egress control, exposed ports, environment variables, and metadata.InstaVMCloudBucketMountStrategyfor S3/R2/GCS/Azure Blob mounts via rclone+FUSE.- Persistent JuiceFS volume helpers (mount, unmount, list).
- Workspace snapshots with mount-safe tar archiving (unmount, tar, remount).
- Exit-code auto-detection: uses native API
exit_codewhen available, falls back to sentinel wrapping on older backends. - Egress policy management (allow/block HTTP/HTTPS, package managers, domain/CIDR allowlists).
- 125 unit tests, 21 e2e integration tests, full
Runner.runagent smoke test verified. - CI matrix: Python 3.10–3.13, Ubuntu/macOS/Windows, plus early-warning job tracking
openai-agents @ git+main.
0.17.0
- Added CLI parity for
egress,exec, and terminal-firstbrowserworkflows - Added colored VM status badges,
--watchrefreshes forlsandsnapshot get, and snapshot-build spinner coverage
0.16.1
- Fixed deploy and cookbook uploads through the SSH gateway by forcing legacy SCP mode
0.16.0
- Expanded CLI docs for
instavm cookbook - Added experimental
instavm deployfor zero-config app deploys from the current directory
0.15.1
lsnow matches the SSH gateway: active VMs by default,-aor--allfor all VM recordswhoaminow uses the live/v1/users/meendpoint
0.15.0
- Installed
instavmCLI forpip install instavm, includingpython -m instavm.cli - Stored CLI auth/config in
~/.instavm/config.jsonwithINSTAVM_API_KEYfallback - Added
get_current_user()andget_session_status(session_id=None)helpers for account and desktop workflows
0.13.0
get_session_app_url(session_id?, port?)— session app URL with optional portlist_sandboxes(metadata?, limit?)— list sandbox records with metadata filteringcomputer_use.head(session_id, path)— HEAD proxy method for computer-use sessionscomputer_use.vnc_websockify(session_id)— VNC websockify URL for remote desktop streaming- VM creation now accepts
volumesfor pre-attached volume mounts
0.12.0
- Manager-based APIs across VMs, volumes, snapshots, shares, custom domains, computer use, API keys, audit, and webhooks
- Snapshot build args support for env vars and Git clone inputs
- Distinct VM list helpers for
/v1/vmsand/v1/vms/
For detailed history, see repository tags and PR history.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file instavm-0.25.0.tar.gz.
File metadata
- Download URL: instavm-0.25.0.tar.gz
- Upload date:
- Size: 254.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4270f83dea4c6e5832f2a295f0ff8d3a9f08a4e55ddf9acd95e6c940da2ef4c2
|
|
| MD5 |
c53b00f1ab34627881b815d1266d9367
|
|
| BLAKE2b-256 |
88de2b958bb15c171e48d04a9960f55e0c1d3ce4d21d02186a58a8688a90f065
|
File details
Details for the file instavm-0.25.0-py3-none-any.whl.
File metadata
- Download URL: instavm-0.25.0-py3-none-any.whl
- Upload date:
- Size: 165.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
52ae9407e9b2c29db86bace3360f283fbc21636cffc1e8d3b75348cf19606b4a
|
|
| MD5 |
353ca7a9aa4f32876d5abecdf1cf2cba
|
|
| BLAKE2b-256 |
23ac5301ec2d11231318b2abde4813fa41376b06cee538d097b332dc051cc04a
|