Measured · every input published

The Node Hour Index

One number per GPU class, in dollars per GPU-hour. It is not a model output and not an opinion — it is a vendor's own on-demand rate, read through a public API and normalised onto a canonical market. The query is below. Run it and you get the same numbers we do.

Live inputs

reading…
Provider SKUVRAMOn-demand $/h Spot $/hCanonical market
Reading provider rate cards…

How the number is made

1

Poll. The provider's public GraphQL endpoint is queried for every GPU type it sells, with the lowest on-demand and spot price for a single card. No API key is involved, so the call is reproducible by anyone.

2

Normalise. Vendors sell SKUs, not classes: H100 SXM, H100 NVL and H100 PCIe are three products. Each canonical market maps to an ordered list of SKU patterns and takes the first that is quoted, so the same physical class always resolves the same way.

3

Publish. The response is served with its fetch timestamp and the full unnormalised list, which is the table above. Nothing is smoothed, filtered or adjusted between the provider's number and the one shown.

4

Cache. Responses are cached 60 seconds at the edge and served stale for up to 10 minutes while revalidating, so a provider outage degrades the index instead of breaking it. A stale read is flagged as stale.

5

Hold the roster. The market list is fixed, not "whatever came back". A provider stops quoting a SKU the minute it runs out of cards — RTX 4090 and L40S drop out several times an hour — so an unquoted market keeps its last measured price with the timestamp it was measured at, marked stale, and is never dropped. The count of markets, the lowest print and the top-to-bottom spread are computed over that fixed roster, so they do not jump when one vendor sells out. A market with no measurement at all reads no quote rather than borrowing a number from somewhere.

Reproduce it

curl -s -X POST https://api.runpod.io/graphql \ -H 'content-type: application/json' \ -d '{"query":"{ gpuTypes { displayName memoryInGb lowestPrice(input:{gpuCount:1}) { uninterruptablePrice minimumBidPrice } } }"}'

Or read our normalised form directly — it is a plain JSON endpoint with permissive CORS, free to poll: /api/prices

What this index is not, yet

It is single-source

Today every number comes from one provider. That makes it a working reference price, not a benchmark — a benchmark has to survive one vendor repricing. Adding vast.ai, Lambda and TensorDock, then trimming outliers across them, is the next piece of work.

It is unweighted

A rate card says what a vendor asks, not what anyone paid. A real benchmark weights by traded volume. We have no volume to weight by, and inventing one would defeat the point of the page you are reading.

Its history is only starting

Since 2 Oct 2026, 11:33:49 UTC every answer of this endpoint is stored every five minutes, untouched, failed reads included. There is no backfill, so the series is days old, and it is not public yet: a public history endpoint and charts are next. Until then there is still no chart of what an H100 hour cost last month.

It is a floor, not a mid

The query asks for the lowest on-demand price a provider offers for one card. That is a defensible, stable definition, but it sits at the bottom of the distribution rather than the middle of it.