Every value list for a Roblox trading game answers the same question: what is this item worth? We wanted a different number — how many people are actually offering one right now — and the marketplace we read has an endpoint that looks like it gives exactly that.
It does not. Here is what it gives instead, and how we measured the difference.
The endpoint
Traderie hands out listings a page at a time:
/api/<game>/listings?limit=100&page=N
Two things are missing, and both matter:
-
There is no total count. No
total, nocount, noLink: rel="last". You page until it stops. -
It stops early. Every game we tried caps out at the same place: 1,550 listings, 31 pages. Ask for page 32 and you get nothing.
limitis honoured on the plain endpoint and silently ignored on the filtered one, which always returns 50.
So you get the 1,550 most recently updated listings. Not a sample — a window, sorted newest first.
The mistake we shipped
We counted how many of those 1,550 listings mentioned each item and published the result as "how many people are offering one". That reads as a stock measurement: this many exist for sale right now.
It is not. It is however many happened to be touched inside the window — and the window is a duration, not a quantity.
Measuring the window
Each listing carries updated_at. The oldest and newest in a completed run bracket exactly how much time those 1,550 listings covered. That is a free measurement: the data is already in the response.
let from = null, to = null
for (const listing of page) {
const t = Date.parse(listing.updated_at)
if (!from || t < from) from = t
if (!to || t > to) to = t
}
// window in minutes
const minutes = Math.round((to - from) / 60000)
Run on 2026-09-09, three games on the same platform:
| Game | Window covered by 1,550 listings |
|---|---|
| Flee the Facility | 14.3 hours |
| Murder Mystery 2 | 69 minutes |
| Adopt Me | 18 minutes |
The same cap means wildly different things depending on how busy the game is. On the quiet game the window is most of a day. On the busy one it is a coffee break.
The check that settled it
If the count were stock, an item recorded as 0 would have nothing on offer. So we asked per item, using the filtered endpoint:
/api/<game>/listings?itemTags=true&item=<id>&selling=true&auction=false&page=N
Of thirty Adopt Me items sampled, twenty-eight had at least fifty active listings — including items our file had recorded as 0 that day. One item had at least 1,550 standing offers while appearing zero times in the newest 100.
Zero in the window is not an observation of absence. It is an absence of observation.
What the number actually is
It is turnover: how often an item moves through new listings. With the window duration measured alongside it, that becomes a rate — listings per hour — comparable between days and between games.
And turnover discriminates where stock does not. Two MM2 items both answer "300+ active listings" to the exact query; inside the window one appears 51 times and the other zero. The expensive exact count is the less informative number.
The cost of getting it exactly
We priced the honest alternative before rejecting it: one request per item per page, 50 per page, for a catalogue of thousands — up to 33,000 requests a day, and the answer would still be "300+" for most items. Not worth it, and not kind to someone else's servers.
Open data
The daily counts are published under CC BY 4.0, one row per item per day, with the window duration recorded beside each reading so the caveat can never outlive the data:
- Live file and documentation: https://fairstash.app/data
- A worked example for one game: https://fairstash.app/mm2/supply
- Kaggle mirror: https://www.kaggle.com/datasets/fairstash/roblox-daily-trade-listing-counts-mm2-adopt-me
If you are reading a marketplace API that caps its result set, the lesson generalises: check what duration your cap covers before you name the number. A count with an unmeasured window is a sentence with an unknown subject.
Top comments (1)
Some comments may only be visible to logged-in visitors. Sign in to view all comments.