Using VMware vSphere REST API with Python: Automate VM Management and Inventory Without Installing PowerCLI

VMware tutorial - IT technology blog
VMware tutorial - IT technology blog

Quick Start: Connect to vSphere API in 5 Minutes

If you’ve ever had to install PowerCLI on Windows just to run a few VMware management commands, vSphere REST API is exactly what you need. All it takes is Python and the requests library — connect to your entire VMware vSphere 7.x/8.x infrastructure from any machine, with no additional dependencies.

Install the required libraries:

pip install requests urllib3

The code below connects to vCenter, retrieves a session token, and lists all VMs — ready to run out of the box:

import requests
import urllib3

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

VCENTER_HOST = "vcenter.lab.local"
USERNAME = "[email protected]"
PASSWORD = "YourPassword123!"
BASE_URL = f"https://{VCENTER_HOST}/api"

# Create session
session = requests.Session()
session.verify = False  # Lab environment — enable verify in production

# Authenticate — retrieve session token
response = session.post(
    f"{BASE_URL}/session",
    auth=(USERNAME, PASSWORD)
)
token = response.json()
session.headers.update({"vmware-api-session-id": token})

# List all VMs
vms = session.get(f"{BASE_URL}/vcenter/vm").json()
for vm in vms:
    print(f"{vm['name']} | Power: {vm['power_state']} | ID: {vm['vm']}")

If the output looks like this, your API connection is working:

ubuntu-22-web01 | Power: POWERED_ON | ID: vm-101
centos-db-prod  | Power: POWERED_OFF | ID: vm-145
win2022-dc01    | Power: POWERED_ON | ID: vm-203

Deep Dive: How Does the vSphere REST API Work?

Session-based Authentication

The vSphere REST API uses a session token instead of Basic Auth for every request. POST your credentials once, receive a string token, then attach it to the vmware-api-session-id header for all subsequent requests. The token automatically expires after 30 minutes of idle time. Unlike Bearer tokens or OAuth, there’s no refresh flow or complex middleware — the entire implementation fits in 5 lines of code.

Two Base Paths You Need to Know

There are two base paths, and this is the most common source of confusion:

  • /api — The new API, pure RESTful, clean JSON responses. Use this if your environment is on vSphere 7.0+.
  • /rest — The legacy API (deprecated since 8.0), still functional but wraps responses in an extra {"value": ...} layer, which is annoying.

The Swagger UI at https://vcenter-ip/api lets you test endpoints directly in your browser without writing any code. Whenever you’re unsure which endpoint to use, pulling this up is much faster than digging through the documentation.

Most Commonly Used Endpoints

# Inventory
GET /api/vcenter/vm                    # List VMs
GET /api/vcenter/vm/{vm}               # Details for a single VM
GET /api/vcenter/host                  # List ESXi hosts
GET /api/vcenter/datastore             # List datastores
GET /api/vcenter/cluster               # List clusters

# Power operations
POST /api/vcenter/vm/{vm}/power?action=start
POST /api/vcenter/vm/{vm}/power?action=stop
POST /api/vcenter/vm/{vm}/power?action=reset

# Snapshot
GET  /api/vcenter/vm/{vm}/snapshot
POST /api/vcenter/vm/{vm}/snapshot
DELETE /api/vcenter/vm/{vm}/snapshot/{snapshot}

Advanced: Real-World Scripts for Daily Operations

Export Inventory to CSV

This is the script I run at the end of each month to report VM status to the infra team — run it once, export a CSV, drop it in the group chat and you’re done:

import requests
import urllib3
import csv
from datetime import datetime

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

class vSphereClient:
    def __init__(self, host, username, password):
        self.base_url = f"https://{host}/api"
        self.username = username
        self.password = password
        self.session = requests.Session()
        self.session.verify = False
        self._authenticate()

    def _authenticate(self):
        resp = self.session.post(
            f"{self.base_url}/session",
            auth=(self.username, self.password)
        )
        resp.raise_for_status()
        self.session.headers.update({
            "vmware-api-session-id": resp.json()
        })

    def get_vms(self, **filters):
        return self.session.get(
            f"{self.base_url}/vcenter/vm", params=filters
        ).json()

    def get_vm_details(self, vm_id):
        return self.session.get(
            f"{self.base_url}/vcenter/vm/{vm_id}"
        ).json()

    def logout(self):
        self.session.delete(f"{self.base_url}/session")


def export_inventory_csv(client, output_file):
    vms = client.get_vms()
    rows = []
    for vm in vms:
        details = client.get_vm_details(vm["vm"])
        memory_gb = details.get("memory", {}).get("size_MiB", 0) / 1024
        cpu_count = details.get("cpu", {}).get("count", 0)
        rows.append({
            "Name": vm["name"],
            "ID": vm["vm"],
            "Power State": vm["power_state"],
            "CPU": cpu_count,
            "Memory (GB)": round(memory_gb, 1),
            "Guest OS": details.get("guest_OS", "Unknown"),
        })

    with open(output_file, "w", newline="", encoding="utf-8") as f:
        writer = csv.DictWriter(f, fieldnames=rows[0].keys())
        writer.writeheader()
        writer.writerows(rows)
    print(f"Exported {len(rows)} VMs to {output_file}")


client = vSphereClient("vcenter.lab.local", "[email protected]", "Password123!")
timestamp = datetime.now().strftime("%Y%m%d_%H%M")
export_inventory_csv(client, f"vm_inventory_{timestamp}.csv")
client.logout()

Bulk Power Management

Shut down all VMs matching a name pattern — useful when you need to perform host maintenance or clean up a test environment:

def bulk_power_off(client, name_pattern):
    vms = client.get_vms()
    targets = [
        vm for vm in vms
        if name_pattern.lower() in vm["name"].lower()
        and vm["power_state"] == "POWERED_ON"
    ]
    print(f"Found {len(targets)} VMs matching '{name_pattern}'")
    for vm in targets:
        resp = client.session.post(
            f"{client.base_url}/vcenter/vm/{vm['vm']}/power",
            params={"action": "stop"}
        )
        status = "✓" if resp.status_code == 204 else "✗"
        print(f"  {status} {vm['name']}")

bulk_power_off(client, "test")

Creating Snapshots Before Updates

My routine before every patch: quick snapshot, apply updates, revert if anything breaks. The script below skips the RAM dump (memory: False) — it’s lighter and works for the vast majority of cases:

def create_snapshot(client, vm_id, name, description=""):
    payload = {
        "description": description,
        "memory": False,    # Skip RAM snapshot — significantly faster
        "name": name,
        "quiesce": True     # Quiesce filesystem if VMware Tools is running
    }
    resp = client.session.post(
        f"{client.base_url}/vcenter/vm/{vm_id}/snapshot",
        json=payload
    )
    if resp.status_code == 204:
        print(f"Snapshot '{name}' created successfully")

create_snapshot(client, "vm-101", "pre-patch-july", "Before July patch Tuesday")

Practical Tips from Daily Operations Experience

Tip 1: Reuse the Session — Don’t Re-authenticate on Every Request

The most common mistake I see is code that authenticates inside a loop — logging in once per VM. vSphere defaults to a limit of 100 concurrent sessions, so this approach quickly triggers 503 Service Unavailable errors when running large batches. Create one vSphereClient instance and use it throughout the entire script.

Tip 2: Use Filter Params to Speed Up Queries

In an environment with 500+ VMs, fetching everything and filtering in Python takes around 8–10 seconds. Use API filters and you’re under 1 second:

# Only fetch powered-on VMs in a specific cluster
params = {
    "filter.power_states": "POWERED_ON",
    "filter.clusters": "domain-c10"  # Cluster ID obtained from GET /api/vcenter/cluster
}
vms = client.session.get(f"{client.base_url}/vcenter/vm", params=params).json()

Bandwidth usage drops proportionally as well — important when running scripts from a laptop over VPN.

Tip 3: Handle Session Timeouts for Long-Running Scripts

Tokens expire after 30 minutes of idle time. An export script for 500+ VMs with full details — one API call per VM — can easily run past the 30-minute mark. Here’s a clean re-authentication implementation:

def safe_get(client, endpoint):
    resp = client.session.get(f"{client.base_url}{endpoint}")
    if resp.status_code == 401:
        # Session expired — re-authenticate
        client._authenticate()
        resp = client.session.get(f"{client.base_url}{endpoint}")
    resp.raise_for_status()
    return resp.json()

Tip 4: SSL Certificates in Production

Lab scripts disable SSL verification for convenience, but you shouldn’t do this in production. The right approach is to use vCenter’s CA certificate:

# Download the cert bundle from vCenter (returns a zip file)
curl -o vcenter-certs.zip "https://vcenter.corp.local/certs/download"
unzip vcenter-certs.zip -d vcenter-certs
# CA cert is located in vcenter-certs/certs/lin/ (Linux) or certs/win/ (Windows)
# Rename the .0 extension to .pem, then use it in Python:
session.verify = "/path/to/vcenter-certs/certs/lin/root-ca.pem"

Tip 5: Mapping PowerCLI Commands to REST API

VMware documentation sometimes only provides PowerCLI examples. The mapping is quite consistent: Get-VMGET /api/vcenter/vm, Start-VMPOST /api/vcenter/vm/{id}/power?action=start, New-SnapshotPOST /api/vcenter/vm/{id}/snapshot. Once you’re familiar with this pattern, the conversion becomes second nature.

An interesting side note from when I migrated my lab to Proxmox: Proxmox’s API also uses a similar session token approach (endpoint /api2/json/access/ticket), so most of the Python code written for vSphere works immediately — just swap out the endpoints and field names. This pattern isn’t locked into VMware.

vSphere’s REST API is stable across versions — scripts written for 7.0 mostly run on 8.0 without modification. That’s why I choose the REST API over PowerCLI for automation: fewer dependencies, runs on any OS, and integrates directly with Ansible, Prometheus, or Slack notifications via Python — no additional middleware or wrappers required.

Share: