Skip to content
Pipelines and Pizza 🍕
Go back

Nutanix REST API Fundamentals

13 min read

Everything in Nutanix is an API call. Whether you’re clicking buttons in Prism or running Terraform, it all comes down to REST. This week we’ll learn the fundamentals — authentication, endpoints, and practical curl examples you can run today.

Why Learn the API?

Let me tell you about a failed DR test.

We had retired some servers — deleted the VMs, closed the tickets, moved on. Except those VMs were still in our DR recovery plans. Still tagged with replication policies. The data was technically still replicating to our DR site, protecting ghosts.

When we ran our DR failover test, it failed. We worked with Nutanix support (who were excellent), but we had to reschedule. Dozens of people lost their weekends — twice — because of orphaned entries we didn’t know existed.

Here’s the thing: this information exists in Nutanix, but the UI doesn’t surface it as a single report. You can see VMs. You can see protection policies. You can see recovery plans. But there’s no “show me the mismatches” button.

That’s where the API comes in. By querying multiple endpoints and cross-referencing the data, we built daily reporting that catches these orphans before they blow up a DR test. The API lets you build the reports that don’t exist in the UI.

Before we dive in: read the Nutanix API documentation and get comfortable with a tool like Postman. Experiment there before writing scripts.

Table of Contents

Open Table of Contents

Prism Central vs Prism Element

Before making API calls, you need to know which endpoint to hit.

Prism Element manages a single cluster. It’s the local management interface running on your Nutanix cluster. Use it for cluster-specific operations like storage containers and host management.

Prism Central manages multiple clusters from a single pane of glass. It’s where you’ll do most of your automation work — VM management, policies, and cross-cluster operations.

FeaturePrism ElementPrism Central
ScopeSingle clusterMultiple clusters
API Versionsv2.0 onlyv3, v4
Port94409440
Use CaseStorage, hosts, local opsVMs, policies, automation

Key takeaway: If you’re automating VM operations, you want Prism Central. If you’re managing storage containers, you need Prism Element.


API Versions: v2, v3, and v4

Nutanix has evolved its APIs over time. Here’s what you need to know:

v2.0 API (Prism Element only)

  • Standard RESTful GET/POST/PUT/DELETE
  • Used for cluster-local resources
  • Deprecating after Q4 2026
GET https://<prism-element>:9440/api/nutanix/v2.0/storage_containers

v3 API (Prism Central only)

  • Uses POST for list operations (intentful design)
  • Requires JSON body with kind parameter
  • Still supported but migration to v4 recommended
POST https://<prism-central>:9440/api/nutanix/v3/vms/list
Body: {"kind":"vm"}
  • Proper RESTful semantics
  • Organized by namespace (vmm, clustermgmt, storage)
  • Supports API key authentication
  • Use this for all new development
GET https://<prism-central>:9440/api/vmm/v4.0/vms

Recommendation: Use v4 APIs for new projects. They’re cleaner, better documented, and won’t be deprecated anytime soon.


Authentication Methods

Basic Authentication (All Versions)

The simplest method — works everywhere.

curl -X GET "https://192.168.1.100:9440/api/clustermgmt/v4.0/clusters" \
  -H "Accept: application/json" \
  --basic \
  --user "admin:YourPassword123" \
  --insecure

The --insecure flag bypasses certificate verification. In production, use proper certificates.

API Key Authentication (v4 only)

Better for automation — no passwords in scripts.

  1. Create a service account in Prism Central
  2. Generate an API key (save it immediately — shown only once)
  3. Use the X-Ntnx-Api-Key header
curl -X GET "https://192.168.1.100:9440/api/vmm/v4.0/vms" \
  -H "Accept: application/json" \
  -H "X-Ntnx-Api-Key: your-api-key-here" \
  --insecure

Your First API Call

Let’s verify connectivity by listing clusters. Set up your environment first:

# Set your variables
export PC_IP="192.168.1.100"
export PC_USER="admin"
export PC_PASS="YourPassword123"

Now list your clusters:

curl -s -X GET "https://$PC_IP:9440/api/clustermgmt/v4.0/clusters" \
  -H "Accept: application/json" \
  --basic \
  --user "$PC_USER:$PC_PASS" \
  --insecure | jq '.data[] | {name: .name, uuid: .extId}'

If that works, you’re connected. If not, check the Troubleshooting Guide.


Common Operations with curl

List All VMs (v3 API)

The v3 API uses POST for list operations:

curl -s -X POST "https://$PC_IP:9440/api/nutanix/v3/vms/list" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  --basic \
  --user "$PC_USER:$PC_PASS" \
  --insecure \
  -d '{
    "kind": "vm",
    "length": 100,
    "offset": 0
  }' | jq '.entities[] | {name: .spec.name, uuid: .metadata.uuid, power: .spec.resources.power_state}'

List All VMs (v4 API)

The v4 API uses proper GET:

curl -s -X GET "https://$PC_IP:9440/api/vmm/v4.0/vms" \
  -H "Accept: application/json" \
  --basic \
  --user "$PC_USER:$PC_PASS" \
  --insecure | jq '.data[] | {name: .name, uuid: .extId}'

Filter VMs by Name

Find VMs matching a pattern (v3 API):

curl -s -X POST "https://$PC_IP:9440/api/nutanix/v3/vms/list" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  --basic \
  --user "$PC_USER:$PC_PASS" \
  --insecure \
  -d '{
    "kind": "vm",
    "filter": "name==prod-*"
  }' | jq '.entities[] | .spec.name'

Get VM Details

Retrieve a specific VM by UUID:

VM_UUID="your-vm-uuid-here"

curl -s -X GET "https://$PC_IP:9440/api/nutanix/v3/vms/$VM_UUID" \
  -H "Accept: application/json" \
  --basic \
  --user "$PC_USER:$PC_PASS" \
  --insecure | jq '.spec'

Power Operations

Power on a VM:

VM_UUID="your-vm-uuid-here"

# First, get the current VM spec
CURRENT_SPEC=$(curl -s -X GET "https://$PC_IP:9440/api/nutanix/v3/vms/$VM_UUID" \
  -H "Accept: application/json" \
  --basic \
  --user "$PC_USER:$PC_PASS" \
  --insecure)

# Update power state
echo "$CURRENT_SPEC" | jq '.spec.resources.power_state = "ON"' | \
curl -s -X PUT "https://$PC_IP:9440/api/nutanix/v3/vms/$VM_UUID" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  --basic \
  --user "$PC_USER:$PC_PASS" \
  --insecure \
  -d @-

List Storage Containers (Prism Element)

Storage containers require the v2 API on Prism Element:

PE_IP="192.168.1.50"  # Prism Element IP

curl -s -X GET "https://$PE_IP:9440/api/nutanix/v2.0/storage_containers" \
  -H "Accept: application/json" \
  --basic \
  --user "$PC_USER:$PC_PASS" \
  --insecure | jq '.entities[] | {name: .name, uuid: .storage_container_uuid}'

List Images

curl -s -X GET "https://$PC_IP:9440/api/vmm/v4.0/content/images" \
  -H "Accept: application/json" \
  --basic \
  --user "$PC_USER:$PC_PASS" \
  --insecure | jq '.data[] | {name: .name, type: .type, sizeBytes: .sizeBytes}'

List Clusters with Details

curl -s -X GET "https://$PC_IP:9440/api/clustermgmt/v4.0/clusters" \
  -H "Accept: application/json" \
  --basic \
  --user "$PC_USER:$PC_PASS" \
  --insecure | jq '.data[] | {
    name: .name,
    uuid: .extId,
    hypervisor: .config.hypervisorTypes[0],
    nodes: .config.nodes | length
  }'

Real-World Example: DR Orphan Detection

Remember that failed DR test I mentioned? Here’s how to catch those orphans before they bite you.

The goal: cross-reference VMs, protection policies, and recovery plans to find mismatches.

List Protection Policies

curl -s -X POST "https://$PC_IP:9440/api/nutanix/v3/protection_rules/list" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  --basic \
  --user "$PC_USER:$PC_PASS" \
  --insecure \
  -d '{"kind": "protection_rule", "length": 100}' | \
  jq '.entities[] | {name: .spec.name, uuid: .metadata.uuid, categories: .spec.resources.categories}'

List Recovery Plans

curl -s -X POST "https://$PC_IP:9440/api/nutanix/v3/recovery_plans/list" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  --basic \
  --user "$PC_USER:$PC_PASS" \
  --insecure \
  -d '{"kind": "recovery_plan", "length": 100}' | \
  jq '.entities[] | {name: .spec.name, uuid: .metadata.uuid}'

Find VMs Protected by a Specific Category

Protection policies use categories to determine which VMs to protect. Query VMs by category:

# Find VMs with category "DR-Tier=Gold"
curl -s -X POST "https://$PC_IP:9440/api/nutanix/v3/vms/list" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  --basic \
  --user "$PC_USER:$PC_PASS" \
  --insecure \
  -d '{
    "kind": "vm",
    "filter": "categories.DR-Tier==Gold",
    "length": 500
  }' | jq '.entities[] | {name: .spec.name, uuid: .metadata.uuid}'

The Orphan Detection Script

This script identifies VMs referenced in protection policies that no longer exist:

#!/bin/bash
# dr_orphan_check.sh - Find orphaned DR configurations

PC_IP="your-prism-central-ip"
PC_USER="admin"
PC_PASS="your-password"

echo "=== DR Orphan Detection Report ==="
echo "Generated: $(date)"
echo ""

# Get all existing VM UUIDs
echo "Fetching VM inventory..."
curl -s -X POST "https://$PC_IP:9440/api/nutanix/v3/vms/list" \
  -H "Content-Type: application/json" \
  --basic --user "$PC_USER:$PC_PASS" --insecure \
  -d '{"kind":"vm","length":500}' | \
  jq -r '.entities[] | .metadata.uuid' | sort > /tmp/existing_vms.txt

VM_COUNT=$(wc -l < /tmp/existing_vms.txt | tr -d ' ')
echo "Found $VM_COUNT VMs in cluster"
echo ""

# Get protection policies and their categories
echo "Checking protection policies..."
curl -s -X POST "https://$PC_IP:9440/api/nutanix/v3/protection_rules/list" \
  -H "Content-Type: application/json" \
  --basic --user "$PC_USER:$PC_PASS" --insecure \
  -d '{"kind":"protection_rule","length":100}' > /tmp/policies.json

POLICY_COUNT=$(jq '.entities | length' /tmp/policies.json)
echo "Found $POLICY_COUNT protection policies"
echo ""

# Check each policy for orphaned references
echo "=== Potential Issues ==="
jq -r '.entities[] | "\(.spec.name)|\(.metadata.uuid)"' /tmp/policies.json | \
while IFS='|' read -r POLICY_NAME POLICY_UUID; do
  # Get category filter for this policy
  CATEGORIES=$(curl -s -X GET "https://$PC_IP:9440/api/nutanix/v3/protection_rules/$POLICY_UUID" \
    -H "Accept: application/json" \
    --basic --user "$PC_USER:$PC_PASS" --insecure | \
    jq -r '.spec.resources.categories // empty | to_entries[] | "\(.key)=\(.value[])"' 2>/dev/null)

  if [ -n "$CATEGORIES" ]; then
    echo "Policy: $POLICY_NAME"
    echo "  Categories: $CATEGORIES"
  fi
done

echo ""
echo "=== Summary ==="
echo "Review any policies with categories that no longer match active VMs."
echo "Run this script daily to catch orphans before DR tests."

Why This Matters

The Prism UI shows you:

  • A list of VMs
  • A list of protection policies
  • A list of recovery plans

What it doesn’t show you: the gaps between them. A deleted VM might still be:

  • Referenced in a recovery plan
  • Tagged with a category that a protection policy is watching
  • Generating replication traffic to your DR site

The API lets you connect these dots. Run this check daily, and you’ll never fail a DR test because of orphaned entries again.


Error Handling

Common HTTP Status Codes

CodeMeaningWhat to Do
200SuccessYou’re good
201CreatedResource created successfully
400Bad RequestCheck your JSON syntax
401UnauthorizedCheck credentials
403ForbiddenCheck permissions or wrong endpoint (v3 on PE)
404Not FoundCheck UUID or endpoint path
409ConflictResource already exists or state conflict
429Rate LimitedSlow down, implement backoff
500Server ErrorCheck Prism Central logs

Debugging Failed Requests

Add -v for verbose output:

curl -v -X GET "https://$PC_IP:9440/api/clustermgmt/v4.0/clusters" \
  -H "Accept: application/json" \
  --basic \
  --user "$PC_USER:$PC_PASS" \
  --insecure

Check the HTTP status code separately:

curl -s -o /dev/null -w "%{http_code}" \
  -X GET "https://$PC_IP:9440/api/clustermgmt/v4.0/clusters" \
  -H "Accept: application/json" \
  --basic \
  --user "$PC_USER:$PC_PASS" \
  --insecure

Rate Limiting and Best Practices

Rate Limits by Prism Central Size

PC SizeRate Limit
X-Small (18GB, 4 CPU)30 req/sec
Small (26GB, 6 CPU)40 req/sec
Large (44GB, 10 CPU)60 req/sec
X-Large (60GB, 14 CPU)80 req/sec

Implement Retry with Backoff

#!/bin/bash
# retry_request.sh - Retry with exponential backoff

make_request() {
  local url=$1
  local max_attempts=5
  local attempt=1
  local backoff=1

  while [ $attempt -le $max_attempts ]; do
    response=$(curl -s -w "\n%{http_code}" -X GET "$url" \
      -H "Accept: application/json" \
      --basic \
      --user "$PC_USER:$PC_PASS" \
      --insecure)

    http_code=$(echo "$response" | tail -n1)
    body=$(echo "$response" | sed '$d')

    if [ "$http_code" == "200" ]; then
      echo "$body"
      return 0
    elif [ "$http_code" == "429" ]; then
      echo "Rate limited. Waiting ${backoff}s..." >&2
      sleep $backoff
      backoff=$((backoff * 2))
      attempt=$((attempt + 1))
    else
      echo "Error: HTTP $http_code" >&2
      echo "$body" >&2
      return 1
    fi
  done

  echo "Failed after $max_attempts attempts" >&2
  return 1
}

make_request "https://$PC_IP:9440/api/clustermgmt/v4.0/clusters"

Best Practices

  1. Use pagination for large result sets — don’t fetch 10,000 VMs at once
  2. Cache cluster UUIDs — they don’t change often
  3. Use API keys for automation instead of passwords
  4. Implement backoff for 429 responses
  5. Log your requests for debugging

Hands-On Lab

Let’s put it all together. You’ll need:

  • Access to Prism Central
  • curl and jq installed
  • Admin credentials

Step 1: Set Up Environment

export PC_IP="your-prism-central-ip"
export PC_USER="admin"
export PC_PASS="your-password"

Step 2: Verify Connectivity

curl -s -X GET "https://$PC_IP:9440/api/clustermgmt/v4.0/clusters" \
  -H "Accept: application/json" \
  --basic \
  --user "$PC_USER:$PC_PASS" \
  --insecure | jq '.data | length'

This should return the number of clusters.

Step 3: List VMs and Their Power States

curl -s -X POST "https://$PC_IP:9440/api/nutanix/v3/vms/list" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  --basic \
  --user "$PC_USER:$PC_PASS" \
  --insecure \
  -d '{"kind":"vm","length":500}' | \
  jq -r '.entities[] | [.spec.name, .spec.resources.power_state] | @tsv' | \
  sort | column -t

Step 4: Count VMs by Power State

curl -s -X POST "https://$PC_IP:9440/api/nutanix/v3/vms/list" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  --basic \
  --user "$PC_USER:$PC_PASS" \
  --insecure \
  -d '{"kind":"vm","length":500}' | \
  jq -r '.entities[] | .spec.resources.power_state' | sort | uniq -c

Step 5: Find VMs Without a Specific Tag

curl -s -X POST "https://$PC_IP:9440/api/nutanix/v3/vms/list" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  --basic \
  --user "$PC_USER:$PC_PASS" \
  --insecure \
  -d '{"kind":"vm","length":500}' | \
  jq -r '.entities[] | select(.metadata.categories == null or .metadata.categories.Environment == null) | .spec.name'

Step 6: Export VM Inventory to CSV

curl -s -X POST "https://$PC_IP:9440/api/nutanix/v3/vms/list" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  --basic \
  --user "$PC_USER:$PC_PASS" \
  --insecure \
  -d '{"kind":"vm","length":500}' | \
  jq -r '["Name","UUID","Power","vCPUs","Memory_MB"], (.entities[] | [.spec.name, .metadata.uuid, .spec.resources.power_state, .spec.resources.num_vcpus_per_socket, .spec.resources.memory_size_mib]) | @csv' > vm_inventory.csv

Troubleshooting Guide

”Connection refused” on port 9440

  • Verify the IP address is correct
  • Check firewall rules allow HTTPS/9440
  • Confirm Prism Central/Element is running

401 Unauthorized

  • Double-check username and password
  • Ensure the account isn’t locked
  • Try logging into the web UI with the same credentials

403 Forbidden with v3 APIs

  • You’re likely hitting Prism Element instead of Prism Central
  • v3 APIs only work on Prism Central
  • Verify you’re using the PC IP, not PE IP

400 Bad Request on v3 list operations

  • v3 requires a JSON body with kind parameter
  • Don’t use GET — use POST for list operations
# Wrong
curl -X GET "https://$PC_IP:9440/api/nutanix/v3/vms/list"

# Correct
curl -X POST "https://$PC_IP:9440/api/nutanix/v3/vms/list" \
  -H "Content-Type: application/json" \
  -d '{"kind":"vm"}'

SSL Certificate Errors

For testing, use --insecure. For production:

curl --cacert /path/to/ca-bundle.crt \
  -X GET "https://$PC_IP:9440/api/clustermgmt/v4.0/clusters"

Slow Responses

  • Reduce length parameter in list requests
  • Add filters to narrow results
  • Check Prism Central resource utilization

Resources

Happy automating!