Key Takeaways
- One public JSON, no key. Live prices and stock for Confidential VMs, standard GPUs and confidential inference models, from the same snapshot the site's pricing pages use.
- Read the fields right. A VM size's price is the whole VM. A standard GPU's price is per GPU, on a machine you rent whole. The feed is cached for 5 minutes.
- A 35-line watcher prints the cheapest GPU in stock per model and alerts when a model comes back. Left running for an hour on 20 October, it caught two restocks.
VoltageGPU publishes its live prices and stock as one public JSON document at https://voltagegpu.com/api/pricing/snapshot. There is no key and no account. The site's pricing pages are built from the same snapshot. This tutorial walks through its structure and explains what each field means, checked against the code that builds it. Then it gives you a 35-line Python script. The script prints the cheapest GPU in stock for each model and alerts you when a model comes back in stock.
Every command below was run on 20 October 2026 on Ubuntu 24.04 with Python 3.12.3 and jq 1.7, in a fresh directory, exactly as printed. Outputs are pasted as printed. Prices and stock are live, so yours will differ.
Step 1: fetch it once
curl -s https://voltagegpu.com/api/pricing/snapshot -o snapshot.json jq 'keys' snapshot.json
[ "confidential", "confidentialCpu", "confidentialVm", "gpus", "inference", "meta", "teeModelCount" ]
confidentialVm: Confidential VMs (Intel TDX), one entry per size intiers.confidential: the same VMs, summed up as one row per GPU model.gpus: standard GPUs, with no enclave.confidentialCpu: confidential CPU servers.inference: per-token prices of the confidential (TEE) models.teeModelCount: how many confidential models there are.meta: when the snapshot was built, plus notes.
Step 2: read the metadata
jq '.meta' snapshot.json
{
"updatedAt": "2026-10-20T13:07:49.258Z",
"stale": false,
"scope": "rentable",
"notes": [
"Only what can be rented today.",
"confidential: one row per GPU model, derived from the Confidential VM tiers (pricePerGpuHour = VM price / GPU count, available = free GPUs). The confidential container tier retired on 2026-10-01 is no longer listed.",
"confidentialVm.tiers[].pricePerHour is the whole VM per hour; pricePerGpuHour = pricePerHour / gpuCount.",
"gpus: standard GPUs with no enclave (not confidential), live stock only.",
"B200 and B300 are listed only when in stock, and NVIDIA GPU attestation is not verified on them."
]
}updatedAt is when the server built this snapshot. stale turns true when the server got no live standard GPUs or no live model prices, and used stored values instead. It does not cover the Confidential VM inventory. If that read fails, confidentialVm.tiers comes back empty. scope: "rentable" means the feed lists only what can be rented today.
Step 3: the Confidential VMs
jq '.confidentialVm.tiers[0]' snapshot.json
jq -c '.confidentialVm.tiers[] | {resourceName, gpuCount, pricePerHour, pricePerGpuHour, available, gpuAttestation}' snapshot.json{
"resourceName": "rtx6000b-small",
"model": "RTX 6000B",
"gpuCount": 1,
"vram": "96 GB",
"pricePerHour": 3.8,
"available": 7,
"gpuAttestation": true,
"pricePerGpuHour": 3.8,
"enclave": "intel-tdx",
"label": "Confidential VM (Intel TDX)"
}
{"resourceName":"rtx6000b-small","gpuCount":1,"pricePerHour":3.8,"pricePerGpuHour":3.8,"available":7,"gpuAttestation":true}
{"resourceName":"h100-small","gpuCount":1,"pricePerHour":6.95,"pricePerGpuHour":6.95,"available":6,"gpuAttestation":true}
{"resourceName":"h200-small","gpuCount":1,"pricePerHour":8.08,"pricePerGpuHour":8.08,"available":0,"gpuAttestation":true}
{"resourceName":"h100-xlarge","gpuCount":8,"pricePerHour":55.62,"pricePerGpuHour":6.95,"available":0,"gpuAttestation":true}
{"resourceName":"h200-xlarge","gpuCount":8,"pricePerHour":64.62,"pricePerGpuHour":8.08,"available":0,"gpuAttestation":false}resourceNameis the size's name. It is the value you pass asresource_namewhen you deploy; the quickstart usesrtx6000b-small.pricePerHouris the whole VM, per hour, in US dollars.pricePerGpuHouris that price divided bygpuCount.availableis how many VMs of that size are free right now. A size at 0 stays in the list.gpuAttestationis true when we have run NVIDIA GPU attestation successfully inside a VM of that model and size. It is false forh200-xlarge, the 8x H200 node, where we have not run it.enclaveisintel-tdxon every VM.
Next to tiers, confidentialVm repeats the fields of a single size (h200-small) for older readers of the feed. Read tiers.
Step 4: the same VMs, per GPU model
jq -c '.confidential[]' snapshot.json
{"model":"H200","vram":"141 GB","pricePerGpuHour":8.08,"available":0,"maxGpuCount":8,"provider":"confidential"}
{"model":"H100","vram":"80 GB","pricePerGpuHour":6.95,"available":6,"maxGpuCount":8,"provider":"confidential"}
{"model":"RTX 6000B","vram":"96 GB","pricePerGpuHour":3.8,"available":7,"maxGpuCount":1,"provider":"confidential"}Each row is computed from the tiers above. pricePerGpuHour is the cheapest GPU-hour among the sizes in stock, or among all sizes when none is (H200 here). available counts free GPUs: free VMs times GPUs per VM. For H100 that is six free h100-small VMs with one GPU each. maxGpuCount is the largest size, here the 8-GPU h100-xlarge, even though none is free. Rows are sorted most expensive first.
Step 5: the standard GPUs
jq '.gpus[] | select(.model == "RTX 4090")' snapshot.json
jq -c '.gpus[] | {model, pricePerHour, available}' snapshot.json{
"model": "RTX 4090",
"vram": "24 GB",
"pricePerHour": 1.12,
"available": 13,
"provider": "standard",
"enclave": "none",
"gpuAttestation": false,
"label": "Standard GPU (no enclave, not confidential)"
}
{"model":"RTX A4000","pricePerHour":0.24,"available":8}
{"model":"RTX 5060 Ti","pricePerHour":0.34,"available":6}
{"model":"RTX 5070 Ti","pricePerHour":0.8,"available":1}
{"model":"B300","pricePerHour":19,"available":32}
{"model":"B200","pricePerHour":9,"available":41}
{"model":"H100","pricePerHour":4,"available":66}
{"model":"A100","pricePerHour":2.38,"available":16}
{"model":"RTX PRO 6000","pricePerHour":4.18,"available":8}
{"model":"RTX 5090","pricePerHour":1.6,"available":9}
{"model":"RTX 4090","pricePerHour":1.12,"available":13}
{"model":"L40S","pricePerHour":0.76,"available":1}
{"model":"L40","pricePerHour":0.66,"available":1}
{"model":"RTX 6000","pricePerHour":1.6,"available":13}
{"model":"A6000","pricePerHour":0.84,"available":3}Standard machines have no enclave. The feed says so in enclave, gpuAttestation and label, so you can filter them out.
Here pricePerHour is per GPU-hour: the lowest per-GPU price among free machines of that model. available counts the GPUs on machines that can be rented whole right now. A standard machine is rented whole, so a machine with 8 GPUs costs 8 times its per-GPU price, per hour. A model with no free machine is left out of gpus entirely, unlike a VM size, which stays at 0. vram can be an empty string when the site has no figure for a model; that was the case for the RTX A4000 that day.
Step 6: inference prices
jq '.inference[0]' snapshot.json jq -c '[(.inference | length), .teeModelCount]' snapshot.json
{
"id": "42ee92ba-a537-5a73-8741-876067750db7",
"name": "google/gemma-4-31B-turbo-TEE",
"category": "LLM",
"priceInputPerM": 0.22,
"priceOutputPerM": 0.68
}
[14,14]These are US dollars per million input and output tokens, for the confidential models. name is the exact model id the API expects. The list is read from the model catalog, at most 15 rows. teeModelCount is the number of confidential models. On 20 October both were 14.
Step 7: how fresh it is
curl -s -D - -o /dev/null https://voltagegpu.com/api/pricing/snapshot | grep -i -E '^(cache-control|age|x-vercel-cache):'
age: 331 cache-control: public, max-age=60, s-maxage=300, stale-while-revalidate=600 x-vercel-cache: STALE
The edge keeps a copy for 5 minutes. For 10 more minutes it may serve that copy while it fetches a new one in the background. That is what STALE and an age of 331 seconds mean here. The server also keeps its own build for 5 minutes. So asking more often than every 5 minutes gets you the same answer, and the data can be several minutes old. meta.updatedAt tells you how old.
Step 8: a watcher in 35 lines
Save this as gpu_watch.py. It uses only Python's standard library.
"""Cheapest in-stock GPU per model, from VoltageGPU's public pricing feed.
Run it every 5 minutes at most (the feed is cached for 5). It prints an ALERT
for each model in stock now that was not in stock on the previous run."""
import json
import sys
import urllib.request
from pathlib import Path
FEED = "https://voltagegpu.com/api/pricing/snapshot"
STATE = Path(sys.argv[1] if len(sys.argv) > 1 else "gpu-watch-state.json")
request = urllib.request.Request(FEED, headers={"User-Agent": "gpu-watch/1.0"})
with urllib.request.urlopen(request, timeout=20) as response:
feed = json.load(response)
# Confidential VM tiers stay listed at 0 when sold out; standard rows only when in stock.
offers = [(f'{t["model"]} confidential', t["pricePerGpuHour"],
f'{t["resourceName"]}: {t["available"]} VM free, {t["gpuCount"]} GPU each')
for t in feed["confidentialVm"]["tiers"] if t["available"] > 0]
offers += [(f'{g["model"]} standard', g["pricePerHour"], f'{g["available"]} GPU free, no enclave')
for g in feed["gpus"] if g["available"] > 0]
cheapest = {} # "H100 confidential" -> (price per GPU-hour, details)
for key, price, details in offers:
if key not in cheapest or price < cheapest[key][0]:
cheapest[key] = (price, details)
print(f'feed built {feed["meta"]["updatedAt"]}, stale: {feed["meta"]["stale"]}')
for key, (price, details) in sorted(cheapest.items(), key=lambda kv: ("standard" in kv[0], kv[1][0])):
print(f"{key:24} ${price:6.2f}/GPU-h {details}")
seen = set(json.loads(STATE.read_text())) if STATE.exists() else set(cheapest)
for key in sorted(set(cheapest) - seen):
print(f"ALERT: {key} is in stock again, ${cheapest[key][0]:.2f}/GPU-h")
STATE.write_text(json.dumps(sorted(cheapest)))What it does:
- It keeps only offers that are in stock: VM sizes with
available > 0, and standard rows. - It keys each offer by model and kind. "H100 confidential" and "H100 standard" are different products, and they stay apart.
- For each key, it keeps the lowest price per GPU-hour. For a VM it also keeps the size to deploy.
- It prints confidential offers first, then standard ones, each sorted by price.
- It saves the keys that are in stock to a small state file. On the next run, a key that is in stock now but was not last time prints an ALERT. The first run only creates the file.
python3 gpu_watch.py
feed built 2026-10-20T13:07:49.258Z, stale: False RTX 6000B confidential $ 3.80/GPU-h rtx6000b-small: 7 VM free, 1 GPU each H100 confidential $ 6.95/GPU-h h100-small: 6 VM free, 1 GPU each RTX A4000 standard $ 0.24/GPU-h 8 GPU free, no enclave RTX 5060 Ti standard $ 0.34/GPU-h 6 GPU free, no enclave L40 standard $ 0.66/GPU-h 1 GPU free, no enclave L40S standard $ 0.76/GPU-h 1 GPU free, no enclave RTX 5070 Ti standard $ 0.80/GPU-h 1 GPU free, no enclave A6000 standard $ 0.84/GPU-h 3 GPU free, no enclave RTX 4090 standard $ 1.12/GPU-h 13 GPU free, no enclave RTX 5090 standard $ 1.60/GPU-h 9 GPU free, no enclave RTX 6000 standard $ 1.60/GPU-h 13 GPU free, no enclave A100 standard $ 2.38/GPU-h 16 GPU free, no enclave H100 standard $ 4.00/GPU-h 66 GPU free, no enclave RTX PRO 6000 standard $ 4.18/GPU-h 8 GPU free, no enclave B200 standard $ 9.00/GPU-h 41 GPU free, no enclave B300 standard $ 19.00/GPU-h 32 GPU free, no enclave
The H200 Confidential VM is missing from the list because none was free. The state file and a second run:
cat gpu-watch-state.json
["A100 standard", "A6000 standard", "B200 standard", "B300 standard", "H100 confidential", "H100 standard", "L40 standard", "L40S standard", "RTX 4090 standard", "RTX 5060 Ti standard", "RTX 5070 Ti standard", "RTX 5090 standard", "RTX 6000 standard", "RTX 6000B confidential", "RTX A4000 standard", "RTX PRO 6000 standard"]
python3 gpu_watch.py | grep ALERT || echo "no alert"
no alert
Step 9: leave it running
Run it every 5 minutes. A loop is enough to try it; for good, use cron or a systemd timer. Here is the loop we left running for an hour on 20 October:
date -u +%FT%TZ > watch.log for i in $(seq 12); do python3 gpu_watch.py | grep -E "^(feed built|ALERT)"; sleep 300; done >> watch.log 2>&1 date -u +%FT%TZ >> watch.log cat watch.log
2026-10-20T12:55:00Z feed built 2026-10-20T12:53:11.535Z, stale: False feed built 2026-10-20T12:58:48.555Z, stale: False feed built 2026-10-20T13:04:13.105Z, stale: False ALERT: L40S standard is in stock again, $0.76/GPU-h feed built 2026-10-20T13:07:49.258Z, stale: False feed built 2026-10-20T13:13:38.191Z, stale: False feed built 2026-10-20T13:13:38.191Z, stale: False feed built 2026-10-20T13:21:00.445Z, stale: False feed built 2026-10-20T13:26:13.620Z, stale: False ALERT: H200 standard is in stock again, $8.14/GPU-h feed built 2026-10-20T13:31:26.465Z, stale: False feed built 2026-10-20T13:36:39.415Z, stale: False feed built 2026-10-20T13:41:52.421Z, stale: False feed built 2026-10-20T13:47:06.377Z, stale: False 2026-10-20T13:57:31Z
That is twelve polls, five minutes apart. The times are when each snapshot was built, not when we polled. Two polls in a row got the same snapshot (13:13:38): that is the cache from step 7. Two alerts fired. An L40S standard GPU came into stock in the snapshot built at 13:04, and an H200 standard GPU in the one built at 13:26. No alert concerned a Confidential VM. The five full snapshots we read that afternoon show the same five sizes, prices and stock.
What this tells you, and what it does not
It tells you the prices and stock the site shows, as of meta.updatedAt. GPU prices are public hourly prices in US dollars.
It does not tell you:
- That a GPU will still be free when you deploy. Stock is a snapshot, and someone else can take the last VM between your read and your deploy.
- What a standard machine costs in total.
gpus[].pricePerHouris per GPU. The machine you rent may have several GPUs, and you pay for all of them. - That a confidential alert is real after a failed read. If the VM inventory cannot be read,
confidentialVm.tierscomes back empty, andmeta.staledoes not flag it. The script then sees every confidential model as gone, and the next good read raises alerts for models that never left. Check thattiersis not empty before you act on a confidential alert. - Anything about the machine you will get.
gpuAttestation: truemeans we have run NVIDIA GPU attestation successfully inside a VM of that model and size. It does not attest your VM. You generate that attestation yourself, inside your VM. The proofs we publish are listed at /proofs. - Confidentiality for standard GPUs. They have no enclave. The feed labels them so.
When the GPU you want shows up, the quickstart deploys a Confidential VM in three API calls. To check the attestation of the VM you get, see Verify an Intel TDX quote from any cloud.