Public API
Base URL for the hub: https://hartiigames.com. Base URL for Biome: https://hartiibiome.com.
Examples below are trimmed real responses captured live against production.
Global state
GET /api/config
Public client config — the kill switch and split constants the client trusts.
{
"paidPlay": false,
"paidGames": ["hash-wars"],
"entryAsset": "QUAI",
"playerAllocationBps": 7500,
"reserveAllocationBps": 2500
}paidPlay: false (live at doc time) means ranked play is globally disabled — the client hides
all stake UI when this is false. Cache-Control: public, max-age=30.
GET /api/pools
{ "paidPlay": false, "pools": {}, "note": "paid play is not yet enabled" }When paidPlay is true, pools becomes { [gameId]: poolAddress } for each game with ranked
enabled (currently only hash-wars would appear, per PAID_GAME_IDS in source).
GET /api/health
{ "ok": true, "rpc": true, "price": true, "d1": { "ok": true, "missing": [] }, "at": 1790174499542 }GET /api/transparency
Public accounting summary — proceeds split, entry-fee split, live pool balances, cosmetics creator-economy rules.
{
"chain": { "id": 9, "name": "Quai mainnet, Cyprus-1" },
"proceedsSplit": { "playersPct": 50, "buybacksPct": 50 },
"entry": { "paidPlay": false, "playerAllocationBps": 7500, "reserveAllocationBps": 2500 },
"pools": [{ "game": "hash-wars", "address": "0x0056C742a880eC0A55b87Db3a893F2a92E94dD89", "balanceQuai": 0 }],
"creators": { "rule": { "tradeFeeBps": 100, "creatorShareBps": 5000, "maxTradeFeeBps": 300 } }
}GET /api/gas
One cached gas quote for the client's txs — measured, not guessed (source comment: a real
enterRanked() used 24,771 gas, 2026-07-05).
{
"gasPriceWei": "29482625609348",
"usdPer21k": 0.00636966,
"usd": { "entry": 0.00758, "approve": 0.01395, "buy1155": 0.03640, "claim": 0.01820 },
"elevated": false
}GET /api/quai-price
{ usd, source, at } — MEXC QUAIUSDT primary, CoinGecko fallback, ~2 min edge cache.
Ranked play
POST /api/ranked/enter
Verify an on-chain enterRanked() (or bare-transfer) payment and issue a run seed.
Body: { gameId, tier, wallet, txHash, ack: true, signature?, ref? }. ack (18+/eligibility
acknowledgement) is required and checked server-side. Checks performed, in order: paid-play kill
switch, jurisdiction gate (cf-ipcountry, edge-verified — not client-suppliable), rate limit
(360/hour), gameId must currently be hash-wars (403 for anything else), tx not already used
(single-use, exactly-once via a D1 unique constraint — not just a racy KV check), one active run
per wallet per game, the tx's to matches that game's pool and from matches the wallet, the
paid value is within −12% / +50% of the tier's USD quote at the current QUAI price (tight
lower bound against underpaying, wide upper bound to absorb price movement between quote and
confirmation — any overpayment simply donates to the pool the player is competing for).
{ "runId": "…", "seed": "ranked-hash-wars-…", "gameId": "hash-wars", "tier": "micro" }Errors are 400/404/409/451 (blocked jurisdiction)/503 (kill switch off, storage
unavailable) with { error }.
POST /api/ranked/submit
Body: { runId, score, inputLog }. The server replays the run itself using the seed it
issued and rejects on any mismatch — a client's claimed score is never trusted. On success it
places the score on the tier's weekly and monthly leaderboards and returns promotion info.
{ "accepted": false, "reason": "replay mismatch", "replayScore": 0 }GET /api/ranked/status, POST /api/ranked/prepare, POST /api/ranked/attach-payment
Supporting steps in the same flow (status polling, a provisional pre-payment run context, and
attaching a late-arriving payment to a provisional run) — see functions/api/ranked/*.js.
GET /api/claimable/:wallet
Reads claimableBalances(wallet) across all ten MonthlyGamePayouts pools in parallel (a pure
read — the actual claim is the wallet's own claimPayout() transaction, this endpoint never
moves funds).
{ "wallet": "0x0031b09e63592b713768761cddacc603974c10bd", "pools": [], "totalQuai": 0, "rewards": null, "hartiiClaimable": 0 }Leaderboards, activity, tournaments
GET /api/leaderboard/:game?board=free|daily|ranked&tier=…&period=weekly|monthly
{ "game": "hash-wars", "board": "free", "key": "lb:free:hash-wars",
"entries": [{ "wallet": "0x00700bd1…", "score": 408832, "at": 1789420663494, "name": "Swimgood" }] }GET /api/dailies[?wallet=0x…]
Today's shared-seed daily challenge summary per game, plus (with a wallet) that wallet's server-computed streak.
{ "date": "20260923", "games": [{ "id": "hash-wars", "top": 0, "players": 0 }], "streak": null }GET /api/activity
Recent verified runs / claims / ranked entries, newest-first, edge-cached
(s-maxage=10, stale-while-revalidate=20) so a burst of viewers costs one origin hit.
{ "items": [] }GET /api/tournaments?status=active|past|all
{ "items": [], "status": "active", "at": "2026-09-23T14:50:45.939Z" }Always returns an honest empty list rather than an error if the sync hasn't run — never a bare
{}.
GET /api/profile/:wallet
XP, level (1 + floor(sqrt(xp/50)), capped at 100), Bronze→Diamond rank tier, per-game bests,
daily streak.
Cosmetics, marketplace, token
GET /api/cosmetics/catalog— shop catalog (type, name, rarity, price, image) plus the liveHartiiCosmeticscontract address.POST /api/cosmetics/equip,GET /api/cosmetics/equipped/:wallet,GET /api/cosmetics/owned/:walletGET /api/marketplace/listings— active cosmetics resale listings, self-heals from chain if its KV index is empty.POST /api/marketplace/record— indexes a listing/sale after the client's own on-chain tx.GET /api/token/config
{ "token": "0x00356B9bc20Ea80C654D77655Da53ca07547c216", "sale": "0x00483bE6EAaA8015130b216e06d8f70723dF822D",
"cosmetics": "0x007fDe17CbA1Fe1750906F08f64fEf44195702eA", "market": "0x0034D51cc6162E27bCfDce5E276d92Eb563bb73C",
"rate": 1000, "decimals": 18, "symbol": "HARTII" }Identity
GET /api/username/:wallet→{ wallet, name }, CORS-open,name: nullif unclaimed.POST /api/username/claim,GET /api/username/check,GET /api/username/resolve
Studio (creator NFT minting) and vault (IPFS document pinning)
functions/api/studio/** (create/mint a creator-submitted art type, eligibility check) and
functions/api/vault/** (nonce, pin, public, list/:wallet — a public, paginated IPFS
document directory) exist and are live but are write-gated by wallet signature per-route; not
detailed further here beyond GET /api/vault/public?limit=&cursor=, a plain paginated read:
{ "items": [{ "cid": "QmWYLKGZSCFHTpv8sgRR4oNCACf5ZQ7DTafcBrcaR1Qmo8", "name": "poetry.txt",
"textBytes": 3555, "at": 1789494765339, "url": "https://ipfs.qu.ai/ipfs/QmWYLKGZ…" }] }Out of scope here
functions/api/qaxe/** integrates with the separate QuaiAxe product (documented elsewhere).
functions/api/sink/**, admin/settlements.js, auth/nonce.js and the daily/* variants are
internal plumbing / operator-only — no public contract documented here beyond "exists and is
gated."
Biome endpoints
Base: https://hartiibiome.com. All three are GET, read-only, no wallet/money logic.
GET /api/chain
{ "blockNumber": 10250128, "gasPriceWei": "29484720446206", "gasPriceGwei": 29484.72 }quai_blockNumber / quai_gasPrice against the public RPC, max-age=45. Fields are null
(never a fabricated value) when the RPC is unreachable.
GET /api/prices
{ "quaiUsd": 0.010354, "changePct": 6.44, "volumeUsd": 93962.9, "qiQuai": 105.025, "qiUsd": 1.087, "spark": [0.009965, "…24 hourly closes"] }MEXC 24h ticker + quai_qiToQuai RPC read for the Qi↔QUAI protocol rate. ~2 min edge cache.
GET /api/ecosystem
Fans out server-side to Games (/api/tournament), Gallery (/api/collections) and Labs, cached
5 minutes per colo, only caches a response if all three sibling fetches succeeded
(x-ecosystem-complete: 1) — a partial failure is never pinned into the cache.
{ "games": { "month": "202609", "gamesWithPlay": 10,
"standings": [{ "wallet": "0x00700bd1…", "points": 5772, "games": 6, "name": "Swimgood" }] } }