Skip to content
DonutStatsAPI
Sign up
Concepts

Caching and freshness

How often each dataset is refreshed, how long you can cache it and how to avoid downloading what you already have.

Each dataset refreshes on its own schedule. Lists carry an updatedAt that tells you when that dataset last changed.

DatasetWhatHow often
Auction houseNew listingsWithin about a minute
The 80 busiest itemsEvery 5 minutes
Every listingAt least every 20 minutes
Buy ordersNew ordersWithin about a minute
The 80 busiest itemsEvery 5 minutes
Every orderAt least every 10 minutes
PricesRecomputed from every open listing and orderEvery 5 minutes
FlipsRecomputed from the live order bookEvery 30 seconds while the market changes
Player statsAny player, on requestLive, see live lookups
Every player on the baltopAt least once a day
LeaderboardsThe whole money board and the top of every other boardEvery 6 hours
BountiesEvery open bountyEvery 15 minutes
Shard shopEvery offerOnce a day
EconomyA sample of baltop and market totalsEvery 15 minutes
ServerPlayers online, latency and versionEvery minute

Responses carry cache-control with a max-age that matches how fast the data can change, plus stale-while-revalidate. Calling again sooner will usually give you the same answer, so cache for at least that long.

cache-control: private, max-age=20, stale-while-revalidate=120

Cached responses also carry an etag. Send it back in if-none-match and, if nothing changed, you get an empty 304 Not Modified instead of the full body.

let etag = null;
let latest = null;

async function poll() {
  const response = await fetch("https://api.donutstats.io/v1/auction/summary", {
    headers: {
      "x-api-key": process.env.DONUTSTATS_API_KEY,
      ...(etag ? { "if-none-match": etag } : {}),
    },
  });
  if (response.status === 304) return latest;
  etag = response.headers.get("etag");
  latest = await response.json();
  return latest;
}

304s still count

A 304 saves bandwidth and parsing, not rate limit. It is still a request. See rate limits.

Endpointmax-ageData updates
/v1Not cachedStatic.
/v1/status10 sLive. Each dataset reports its own updatedAt.
/healthNot cachedLive.
/v1/players30 sBalances update with every lookup and every leaderboard update.
/v1/players/search30 sLive.
/v1/players/{username}15 sLive with lookup=auto or force. With none, as fresh as the last lookup.
/v1/players/{username}/listings20 sAfter a live lookup, at most every 10 minutes per player.
/v1/players/{username}/history1 minA point is added every time the player's stats change.
/v1/players/{username}/ranks1 minA point is added with every leaderboard snapshot.
/v1/leaderboards30 sDonutSMP boards every 6 hours. Kills and shards with every lookup. Bounties every 15 minutes.
/v1/leaderboards/{board}1 minDonutSMP boards every 6 hours. Kills and shards with every lookup. Bounties every 15 minutes.
/v1/leaderboards/{board}/snapshots1 minA new snapshot every 6 hours.
/v1/leaderboards/{board}/snapshots/{id}5 minNever changes.
/v1/auction20 sNew entries within about a minute, the busiest items every 5 minutes and everything else at least every 20 minutes.
/v1/auction/deals20 sWith every auction house refresh, and prices every 5 minutes.
/v1/auction/summary20 sWith every auction house refresh.
/v1/auction/closed20 sWith every auction house refresh.
/v1/auction/history20 sNew entries with every auction house refresh. Kept forever.
/v1/orders20 sNew entries within about a minute, the busiest items every 5 minutes and everything else at least every 20 minutes.
/v1/orders/summary20 sWith every buy order refresh.
/v1/orders/history20 sNew entries with every buy order refresh. Kept forever.
/v1/orders/fills20 sNew entries with every buy order refresh. Kept forever.
/v1/items1 minPrices are recomputed every 5 minutes.
/v1/items/movers2 minPrices every 5 minutes, compared with yesterday's close.
/v1/items/{key}1 minPrices every 5 minutes. Orders and listings as the market refreshes.
/v1/items/{key}/history1 minA new hourly point every hour and a daily point every day. Kept forever.
/v1/flips30 sRecomputed at most every 30 seconds while the market changes.
/v1/bounties30 sEvery 15 minutes.
/v1/bounties/history30 sEvery 15 minutes. Kept forever.
/v1/shards5 minOnce a day.
/v1/economy1 minSampled every 15 minutes. The baltop every 6 hours.
/v1/economy/history1 minA new sample every 15 minutes. Kept forever.
/v1/server30 sEvery minute.
/v1/server/history1 minEvery minute.
/v1/server/samples30 sA new sample every minute. Kept forever.
/v1/images/player/{username}.png10 minRendered from the latest stats, then cached for 10 minutes.
/v1/images/item/{key}.png10 minRendered from the latest prices, then cached for 10 minutes.
/v1/images/leaderboard/{board}.png10 minRendered from the latest leaderboard, then cached for 10 minutes.

GET /v1/players/{username} is only cached when no live lookup ran. Responses that waited for a lookup are marked no-store.

When fresh data stops coming in, for example during a DonutSMP outage, DonutStats stops writing price and economy history until it is back. History series then have a gap rather than a flat line of stale values, so draw missing hours as missing.

Totals that cover the last 24 hours, like paidOut24h in economy history, are reported as null while that window still contains a gap, because the true figure is unknown.