Skip to content
Droptape
01public api

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.

GET /api/current.json bash
# 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.

browser or node js
// 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

response json
{
  "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.