One endpoint, no key.
Everything the site renders comes from a single gzipped JSON document, and that document is public. There is no signup, no token and no rate limit. It is the same bytes the site itself reads, so it cannot drift out of date relative to what you see here.
# One request. No key, no signup, no rate limit.
curl -s https://droptape.theserverless.dev/api/current.json | jq '.market'
# Poll politely: send back the ETag and get a 304 when nothing moved.
curl -s -H 'If-None-Match: "<etag>"' -o /dev/null -w '%{http_code}\n' \
https://droptape.theserverless.dev/api/current.json Polling without being rude
The document is rewritten only when a poll finds a real change, so it is worth asking at most once a minute. Send the ETag back as If-None-Match and you get a 304 with no body when nothing moved. ETag is in Access-Control-Expose-Headers, so this works from a browser too.
// Works from any origin — CORS is open and ETag is exposed.
let etag = null;
async function poll() {
const res = await fetch('https://droptape.theserverless.dev/api/current.json', {
headers: etag ? { 'If-None-Match': etag } : {},
});
if (res.status === 304) return null; // nothing changed since last time
etag = res.headers.get('etag');
return res.json();
}
// The document is rewritten only when a poll finds a real change, so there is
// no value in asking more than once a minute.
setInterval(poll, 60_000); Shape
{
"schemaVersion": 1,
"generatedAt": "2026-08-09T13:49:21.966Z",
"contentHash": "c2c34f91a6de198c",
"meta": {
"generator": "droptape",
"timestamps": "unix seconds, UTC",
"currencies": ["EUR", "USD"],
"pricesIncludeVat": false,
"counts": { "auctionListings": 109, "cloudTypes": 25, "cloudAvailability": 114 }
},
"market": { "listingCount": 109, "minPriceEur": 59, "medianPriceEur": 83, "avgEurPerTb": 44.58 },
"highlights": { "cheapest": {...}, "bestEurPerTb": {...}, "biggestDropToday": {...} },
"facets": { "regions": [...], "datacenters": [...], "specials": [...] },
"auction": { "listings": [ /* every active listing */ ] },
"cloud": { "locations": [...], "types": [...], "availability": [...], "prices": [...] },
"drops": [ /* price reductions observed today */ ]
} Things that will bite you
- schemaVersion
- Increments only on a breaking change — a field removed, renamed, or given a different meaning. Adding a field does not bump it, so pin on this and ignore unknown keys.
- contentHash
- Covers everything except generatedAt. Two responses with the same hash are the same data, which is a cheaper equality check than diffing the document.
- Timestamps
- Unix SECONDS, UTC, everywhere in this document. The database stores milliseconds; the conversion happens once, at the boundary, so you never see a mixed unit here.
- Prices
- EUR, NET of VAT, because that is how Hetzner publishes them. USD is Hetzner’s own figure from the same feed, not a conversion we did. Never assume a rate.
- Disks
- Decimal terabytes. A "4.0 TB" disk is 4000 GB, not 4096. Filtering on 4096 silently matches nothing — this has already caught us.
- available
- For cloud types, true means orderable in that location right now. Availability is confirmed across two consecutive polls before it is reported, so a type that flickers does not appear to flap.
What we promise, and what we do not
Fields will be added over time; unknown keys should be ignored rather than treated as an error. A field will not be removed or change meaning without schemaVersion incrementing. What is not promised is uptime: this is a free endpoint served from one Worker, and the data behind it comes from undocumented Hetzner feeds that have moved before and will move again. If you are building something that must not break, cache your own copy.
Droptape is not affiliated with Hetzner. The data is observed from public feeds, and a listing that vanishes is reported as no longer listed — sold, withdrawn and re-listed under a new id are indistinguishable from outside, so we do not guess.