API documentation
A read-only JSON view of the same market mirror the site renders.
Getting a key
Keys are granted by hand, free, to people building something. Sign in to GnomeStats with Discord once (the grant attaches to an existing account), then ask in the GnomeCorp Discord or message GnomeWhisperer in game with a line about what you are building. There is no self-serve signup and no payment. Keys are read-only: the API exposes the public market mirror and nothing else, moves no money, and holds no balances.
Send the token as a bearer header on every request:
curl -H "Authorization: Bearer YOUR_TOKEN" \ "https://www.gnomestats.com/api/v1/items?sort=spread&pageSize=10"
Scopes
Every key carries one or more of three read-only scopes.
| market:read | Read the global market overview and stats series. |
| items:read | Read item screener rows, item detail, history, and movers. |
| shops:read | Read shop listings and per-shop change history. |
Endpoints
Seven endpoints, all GET, all JSON. The public param names and value grammars are frozen: values may be added (a new sort key, a new alert semantics note below), names never change.
GET /api/v1/itemsitems:read?sort=totalVolume|tradeCount|minBuyPrice|maxSellPrice|activeShopCount|spread|change &dir=asc|desc &page=1 &pageSize=50 &window=7|30|90|365|all &material=STONE &custom=true|false &inStock=true
The screener: one row per item with registry price signals and change%.
GET /api/v1/items/:itemKeyitems:read?window=7|30|90|365|all
One item's page data: aggregates, detail header, daily history. 404 on an unknown key.
GET /api/v1/items/:itemKey/historyitems:read?window=7|30|90|365|all
The per-day accrual rows, oldest first: sales, quantity, dollar volume, blended average.
GET /api/v1/moversitems:read?kind=gainers|losers|volatile|liquidity &window=7|30|90|365|all
Movers boards. accrued:false with empty lists when the window outruns history.
GET /api/v1/shopsshops:read?item=<itemKey> &buyable=true &inStock=true &sort=buyPrice|sellPrice|stock, or without ?item: &material &firm &page &pageSize
Item-scoped registry rows, or the paged shop directory.
GET /api/v1/shops/:shopId/historyshops:readThe shop's change series, oldest first: price, stock, listed, delisted. 400 on a non-numeric id.
GET /api/v1/market/overviewmarket:read?window=7|30|90|365|all
Global totals plus a date-bucketed series at adaptive resolution.
Rate limits
Each key has a per-minute request budget, 120 by default (yours may differ). Every response carries X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset headers; a 429 adds Retry-After. The headers are the authoritative statement of your budget, so read them rather than hard coding a number. Unauthenticated requests share a small per-address budget that exists only to answer with clean errors.
Errors
| code | status | meaning |
|---|---|---|
| MISSING_TOKEN | 401 | No Authorization: Bearer header. |
| INVALID_TOKEN | 401 | The token matches no key. |
| TOKEN_EXPIRED | 401 | The key's expiry has passed. |
| TOKEN_REVOKED | 401 | The key was revoked by an admin. |
| FORBIDDEN | 403 | The key lacks the route's scope. |
| RATE_LIMITED | 429 | Budget exhausted; honour Retry-After. |
| NOT_FOUND | 404 | Unknown item or shop. |
| INTERNAL | 500 | Request failed on our side; retry later. |
Every error body has one shape: {"error":{"code":"...","message":"..."}}.
Reading the numbers honestly
The caveats the site prints beside its own figures apply to yours too. Money values serialize as exact decimal strings, never floats, and big counters as strings where they can pass 2^53; parse accordingly. A buyPrice of 0.00 is a disabled side of the sign, not a free item. avgUnitPrice in the history rows is a buy plus sell blended, unit-weighted average: a liquidity signal, not a clean price. The day field is an opaque YYYY-MM-DD string on the economy server's clock; compare lexicographically and never convert it through a timezone. Registry figures trail the live game by up to a crawl cycle, about two hours for shop stock, and the freshness section carries the full cadence table.
Semantics changes are announced here and in the route headers. 2026-07-17: maxSellPrice everywhere now also excludes passed-over sell listings (payouts the market routed around for a week); values can only tighten, no shape change.