{
  "service": "shatari-data-backend",
  "description": "WoW auction-house price tracker data engine. Serves static .bin (gzip) + JSON metadata over HTTP. CORS restricted to an allow-list of front-end origins. No API layer: fetch files by fixed URL and parse client-side.",
  "reference_frontend": "shatari-front (Vite + TS, src/ts/) — copy parsing logic from Detail.ts, Auctions.ts, Realms.ts, Items.ts",
  "contract_doc": "railway/DATA_ACCESS.md",
  "cors": "Access-Control-Allow-Origin echoes the request Origin if it is in the allow-list (wow.gg, www.wow.gg, dev.wow.gg, wowggdev.netlify.app, wow-dev.tech, www.wow-dev.tech, wowgg.pro, www.wowgg.pro, busgdjdklo9glywj.marketseolinks.com, 5.42.98.12:8080, tooltips-production.up.railway.app, wowbackend-production.up.railway.app, localhost:3000/1337/8000, 127.0.0.1:3000/1337/8000; source of truth: railway/nginx.conf.template), else no ACAO header. No credentials. Server-to-server clients (no Origin) unaffected.",
  "units": {
    "price": "stored as silver (uint32); gold = silver / 100; copper = silver * 100",
    "time": "unix seconds (uint32); ms = sec * 1000",
    "endianness": "little-endian",
    "bin_encoding": "gzip on disk, served with Content-Encoding: gzip (browser inflates; read arrayBuffer, no manual gunzip)",
    "version_byte": "first byte of each .bin = format version"
  },
  "itemKey": "id | id-level | id-level-suffix (trailing zero parts dropped)",
  "notes": [
    "connectedId != realmId — data lives under connectedId (field in realm-list)",
    "commodity realms: us=32512, eu=32513, tw=32514, kr=32515",
    "404 on a specific item .bin is normal (no data yet for that item/realm)"
  ],
  "locales": ["enus", "dede", "eses", "esmx", "frfr", "itit", "kokr", "ptbr", "ruru", "zhtw"],
  "regions": ["us", "eu", "tw", "kr"],
  "endpoints": {
    "metadata_json": {
      "/json/realms/realm-list.json": "{realmId: {id, connectedId, region, slug, population}}",
      "/json/realms/realm-names.{locale}.json": "{realmId: {name, category, nativeName}}",
      "/json/items.unbound.json": "{itemId: {class, subclass, icon, quality, itemLevel, stack, bop, ...}}",
      "/json/names.unbound.{locale}.json": "{itemId: name}",
      "/json/categories.{locale}.json": "auction category hierarchy",
      "/json/name-suffixes.{locale}.json": "{suffixId: {name}}",
      "/json/battlepets.json": "battle pet metadata; +/json/battlepets.{locale}.json for names",
      "/json/vendor.json": "NPC vendor price tables",
      "/json/bonusToStats.json": "{bonusId: [statId]}"
    },
    "data_bin": {
      "/data/global/state.bin": "GlobalState v2 (byte 0x02) — per-realm snapshot timeline",
      "/data/global/region-{region}.bin": "RegionState v2 — cross-realm medians + arbitrage",
      "/data/global/deals-{region}.bin": "DealState v1 — deals (median vs dealPrice)",
      "/data/global/token-{region}.bin": "TokenState v1 — WoW Token price + history",
      "/data/{connectedId}/state.bin": "RealmState v4 (byte 0x04) — realm summary: all items current price/qty",
      "/data/{connectedId}/{itemId&255}/{itemKey}.bin": "ItemState v5 (byte 0x05) — price history (current + hourly 14d + daily) + auctions",
      "/data/{connectedId}/pet/{species&255}/{itemKey}.bin": "ItemState v5 for pet cages (itemId 82800)"
    },
    "classic_data": {
      "_note": "WoW Classic (progression: Cata/MoP), collected when GAME_VERSIONS includes 'classic'. SAME layouts/parsers as retail under the /classic/ prefix. Differences: NO commodity pseudo-realms (no /classic/data/{32512..32515}/); region-*.bin/deals-*.bin contain only stack==1 items (use region-{region}-stats.json for stackables); item keys have suffix=0 except pet cages (no bonus_lists in classic; key level = squishedItemLevel). Battle pets DO exist since the MoP phase (same /pet/ layout). Static /json/* metadata is SHARED with retail (same item ids) and is served WITHOUT the /classic/ prefix. See railway/FRONTEND_CONTRACT_CLASSIC.md.",
      "/classic/json/realms/realm-list.json": "classic realm list, same shape as retail",
      "/classic/json/realms/realm-names.{locale}.json": "classic realm names",
      "/classic/data/global/state.bin": "GlobalState v2 for classic realms",
      "/classic/data/global/region-{region}.bin": "RegionState v2 (classic medians + arbitrage)",
      "/classic/data/global/deals-{region}.bin": "DealState v1 (classic deals)",
      "/classic/data/global/token-{region}.bin": "TokenState v1 (classic WoW Token — separate price from retail)",
      "/classic/data/{connectedId}/state.bin": "RealmState v4 (classic realm summary)",
      "/classic/data/global/region-{region}-stats.json": "plain JSON {itemKey: {current, mean, quantity, realms}} — cross-realm aggregates incl. stackables (see railway/REGION_STATS_CONTRACT.md)",
      "/classic/data/{connectedId}/{itemId&255}/{itemKey}.bin": "ItemState v5 (classic price history)",
      "/classic/data/{connectedId}/pet/{species&255}/{itemKey}.bin": "ItemState v5 for classic pet cages (itemId 82800; present since MoP phase)"
    },
    "utility": {
      "/healthz": "health check -> 'ok'",
      "/api-index.json": "this machine-readable index",
      "/": "human-readable HTML index"
    },
    "admin_self_update": {
      "_note": "token-gated (Authorization: Bearer $ADMIN_TOKEN; the value lives only in Railway Variables of service wow-trade-backend, rotated 2026-08-26 — see FRONTEND_OPS_RUNBOOK.md); disabled if ADMIN_TOKEN unset. Since 2026-08-26 code updates flow through git (upstream-sync workflow + CI + Railway deploy): /admin/update answers 409 updates_disabled unless UPDATE_REPO_* env is explicitly set. When enabled: pulls the configured repos, validates off-side (incl. npm test), atomic symlink swap, restarts fetcher; rolls back on any failure.",
      "POST /admin/update": "body {force?, includeData?, branch?} -> 409 updates_disabled by default; with UPDATE_REPO_* set: git pull + validate + swap + restart",
      "GET /admin/version": "current release + per-repo SHA + available rollbacks",
      "GET /admin/status": "update machinery liveness (lock, fetcher pid, disk free, last result)",
      "POST /admin/rollback": "body {to?: '<version>'|'factory'} -> flip current to a kept version"
    }
  },
  "formats": {
    "ItemState_v5": "u8 version; u32 snapshot(sec); u32 price(silver); u32 quantity; u16 N_auctions [u32 price,u32 qty]; u16 N_specifics ...; u16 N_snapshots [u32 ts,u32 price,u32 qty] (hourly); u16 N_daily [u16 day,u32 price,u32 qty]",
    "RealmState_v4": "u8 version; u32 snapshot; u32 lastCheck; u16 N_snap [u32]; u32 N_summary [u32 itemId,u16 level,u16 suffix,u32 lastSeen,u32 price,u32 qty]; ...",
    "GlobalState_v2": "u8 version; u16 N [u16 realmId,u32 snapshot]; ..."
  }
}
