Hartii Biome docs

Games

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, except where marked "from source": those are written from the code before the release was captured live.

JSON request limits

JSON body limits count raw UTF-8 bytes, including JSON structure and any byte-order mark. Multibyte characters consume their encoded size, so character counts are not a safe size estimate. A Content-Length header is not required for the server to enforce the limit; an oversized stream is rejected before parsing.

Limits remain endpoint-specific: username claim accepts up to 2,048 bytes; ranked preparation, entry, status and payment attachment each accept up to 4,096 bytes; username resolution and studio mint up to 8,192 bytes; studio creation up to 32,768 bytes; replay submissions use their existing larger limits. Count a client payload with new TextEncoder().encode(JSON.stringify(body)).byteLength before submitting. Each endpoint retains its existing failure response, including an empty result for username resolution. Wallet-signature checks remain required where documented.

Global state

GET /api/config

Public client config: the kill switch and split constants the client trusts. Since 1.19.0 the example is written from source (functions/api/config.js).

json
{
  "paidPlay": false,
  "paidGames": [],
  "entryAsset": "QUAI",
  "playerAllocationBps": 7500,
  "reserveAllocationBps": 2500
}

paidPlay: false means ranked play is globally disabled, and the client hides all stake UI when it is false. PAID_PLAY_ENABLED is "false" in wrangler.toml. Cache-Control: public, max-age=30.

Changed in 1.19.0:

  • paidGames is []. It was ["hash-wars"]. It is the server's one list of games that can take a paid entry (PAID_GAME_IDS in functions/api/_lib/gameRoster.js), and no game is on it. The client reads it too, so no game shows a stake screen even if the global switch were turned on.
  • The campaign block that 1.18.0 returned (a visual-only QuaiAxe event) is gone, along with the banner that showed it.

GET /api/pools

json
{ "paidPlay": false, "pools": {}, "note": "paid play is not yet enabled" }

When paidPlay is true, pools becomes { [gameId]: poolAddress } for each game in PAID_GAME_IDS. That list is empty since 1.19.0 (it was hash-wars), so pools is {} even with the switch on, and the response is { "paidPlay": true, "pools": {} }. The shape is unchanged.

GET /api/health

json
{ "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.

json
{
  "chain": { "id": 9, "name": "Quai mainnet, Cyprus-1" },
  "proceedsSplit": { "playersPct": 50, "buybacksPct": 50 },
  "entry": { "paidPlay": false, "paidGames": [], "playerAllocationBps": 7500, "reserveAllocationBps": 2500 },
  "pools": [{ "game": "hash-wars", "address": "0x0056C742a880eC0A55b87Db3a893F2a92E94dD89", "archived": true, "balanceQuai": 0 }],
  "creators": { "rule": { "tradeFeeBps": 100, "creatorShareBps": 5000, "maxTradeFeeBps": 300 } }
}

proceedsSplit.buybacksNote and buybacks.note say what the buyback half does today. QAXE buybacks were stopped on 28 Sep 2026, and the ones that ran stay listed in /data/buybacks.json with their transaction hashes. The treasury holds the HBOME it buys with treasury funds. Of an HBOME buyback paid from collected fees, half is burned and half is held. Nothing else is burned unless the owner orders a burn (the first was 1,111,111 HBOME on 9 Oct 2026). No buyback paid from fees has run yet. The page at hartiigames.com/transparency says the same, and its "Still running" row for QAXE buybacks reads "No".

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).

json
{
  "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 be a game that takes paid entries (403 for any other game, which is every game in 1.19.0; 400 for an unknown id), 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).

json
{ "runId": "…", "seed": "ranked-hash-wars-…", "gameId": "hash-wars", "tier": "micro" }

The example shows the shape. Hash Wars was the only paid game, and no game returns this response in 1.19.0.

Errors are 400/403/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.

json
{ "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.

POST /api/ranked/prepare applies the same roster rule as enter: a game that is not in PAID_GAME_IDS gets 403 with { "error": "paid ranked play is not available for this game" }, and no provisional run is written.

GET /api/claimable/:wallet

Reads claimableBalances(wallet) across all ten MonthlyGamePayouts pools, one after another (a pure read — the actual claim is the wallet's own claimPayout() transaction, this endpoint never moves funds).

json
{ "wallet": "0x0031b09e63592b713768761cddacc603974c10bd", "pools": [], "totalQuai": 0, "rewards": null, "hartiiClaimable": 0 }

The Hash Wars pool is still read, so a prize from it can still be claimed after the game left the roster. hartiiClaimable is the wallet's accrued legacy HARTII rewards, read from the rewards pool named in rewards (null when none is configured). The Me sheet shows a Claim button for it when it is above zero.

Leaderboards, activity, tournaments

Seasons

Since 1.18.0 the free and daily boards are kept per season. A season is one rules version of one game. The version travels in the run's seed, as a token after the game id:

text
<kind>-<game>-v<N>-<rest>        kind: free, ranked, daily or qaxe

A seed with no -v<N>- token uses the game's classic rules. The server replays every submitted run under the rules named in its seed, so a run cannot claim a season it was not played under. The current season is one config line per game (SEASONS in apps/hub/games/_shared/seasons.js). This is what 1.23.0 ships:

Gameseason values you can readDefault (current)
hex-breaker, blade-pong, pulse-snake, shatter-stack, nova-storm, neon-drift, flux-hop, gravity-flip, orbit-lock, piano-keys1 = Season 1, classic rules (archive). 2 = Season 2, rules v2.2
alien-siege1 = Season 1, classic rules (archive). 2 = Season 2, rules v2 (archive). 3 = Season 3, rules v3 (hold to fire).3
hash-wars (off the roster since 1.19.0)2 only. Rules v2 is its classic rules, so its one board counts as Season 1. The archived board is still readable.2

alien-siege and piano-keys went from 1 only to 1 and 2 in 1.19.0, when they came back on the roster. alien-siege added 3 in 1.23.0: its ship fires only while fire is held, and its daily seed is daily-alien-siege-v3-<date>. Hash Wars rules v3 to v5 were practice only and have no board, and the site no longer offers them. Ranked seeds carry no version token, so ranked play stays on each game's classic rules.

GET /api/leaderboard/:game?board=free|daily|ranked&tier=…&period=weekly|monthly

json
{ "game": "hash-wars", "board": "free", "key": "lb:free:hash-wars",
  "season": 2, "seasons": [{ "version": 2, "season": 1, "label": "Season 1 (classic rules)", "current": true, "archive": false }],
  "entries": [{ "wallet": "0x00700bd1…", "score": 408832, "at": 1789420663494, "name": "Swimgood" }] }

season and seasons are new in 1.18.0 and are written here from source (functions/api/leaderboard/[game].js); the rest of the example is the earlier live capture. They are returned for board=free and board=daily only. The example is a Hash Wars board. Hash Wars left the roster in 1.19.0, but GET /api/leaderboard/hash-wars still returns its archived boards. Every other part of the request works as shown for the eleven roster games.

season (optional) picks which season's board to read:

  • It is a rules version: an integer from the table in Seasons. It applies to board=free and board=daily, and is ignored for board=ranked.
  • Omitted or empty: the game's current season.
  • Anything that is not a rules version the game has a board for, such as season=3, season=abc, or season=1 for Hash Wars: 400 with { "error": "unknown season" }.
bash
curl -s "https://hartiigames.com/api/leaderboard/hex-breaker?board=free"            # current season (rules v2)
curl -s "https://hartiigames.com/api/leaderboard/hex-breaker?board=free&season=1"   # Season 1 archive (classic rules)

Response fields for free and daily boards (season and seasons are new):

FieldMeaning
seasonThe rules version this response is for. It is the number you would pass as ?season=.
seasonsEvery board the game has, oldest first.
seasons[].versionThe rules version. Pass it as season.
seasons[].seasonThe season number players see. 1 is the classic rules.
seasons[].labelSeason 1 (classic rules) or Season 2 (rules v2).
seasons[].currenttrue for the season served when season is omitted.
seasons[].archiveThe opposite of current.
keyThe board's storage key. Season 1 keeps its original keys (for example lb:free:<game>). Later seasons add a prefix (for example lb:v2:free:<game>).

For the eleven roster games, season and seasons[].season are the same number. For Hash Wars they are not: season is 2 (the rules version) while its one entry is season number 1.

GET /api/daily/:game

Today's shared daily seed for one game, the same for every player (UTC date). Roster games only: a game that is off the roster, such as hash-wars since 1.19.0, returns 404 with { "error": "unknown game" }. Cache-Control: public, max-age=300.

json
{ "game": "hex-breaker", "date": "20261009", "seed": "daily-hex-breaker-v2-20261009", "rulesVersion": 2 }

The shape above is written from source (functions/api/daily/[game].js); rulesVersion is new in 1.18.0. The daily always runs on the game's current rules, so the seed format depends on the game:

GameDaily seedrulesVersion
the eleven roster gamesdaily-<game>-v2-<YYYYMMDD>, for example daily-hex-breaker-v2-202610092
hash-warsnone: 404 since 1.19.0n/a

Before 1.18.0 every daily seed was daily-<game>-<YYYYMMDD>. A daily run submitted to POST /api/play/track must use the seed this endpoint returns today, or it is refused with 400 (wrong daily seed). An old unversioned daily seed no longer scores for any roster game. The reply to POST /api/play/track now includes rulesVersion, the version the run was scored under. A seed whose rules version has no board, such as -v3- for a game that stops at rules v2, is refused with 403. A run for a game that is off the roster, such as hash-wars, is refused with 400 (unknown game).

GET /api/dailies[?wallet=0x…]

Today's shared-seed daily challenge summary per game, plus (with a wallet) that wallet's server-computed streak. There is one row for each roster game (eleven since 1.19.0, and hash-wars no longer has one). top and players count the game's current-season daily board.

json
{ "date": "20261009", "games": [{ "id": "hex-breaker", "top": 0, "players": 0 }], "streak": null, "streakPlayedToday": null }

Trimmed to one row and written from source (functions/api/dailies.js). streak and streakPlayedToday are null without a wallet, and null (never 0) when the store cannot be read.

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.

json
{ "items": [] }

GET /api/tournaments?status=active|past|all

json
{ "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. byGame keeps bests per season: Season 1 under the plain game id, later seasons as <game>:v<N> (for example hex-breaker:v2).

Cosmetics, marketplace, token

  • GET /api/cosmetics/catalog: shop catalog (type, name, rarity, price, image) plus the live HartiiCosmetics contract address. price is the legacy HARTII price. The Shop never reads it, because skins are priced in HBOME since 1.19.0.
  • POST /api/cosmetics/equip, GET /api/cosmetics/equipped/:wallet, GET /api/cosmetics/owned/:wallet
  • GET /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. The hub only cancels listings now. It no longer buys or lists on the HARTII market, so it calls this after a cancel only.
  • GET /api/token/config
json
{ "token": "0x00356B9bc20Ea80C654D77655Da53ca07547c216", "sale": "0x00483bE6EAaA8015130b216e06d8f70723dF822D",
  "cosmetics": "0x007fDe17CbA1Fe1750906F08f64fEf44195702eA", "market": "0x0034D51cc6162E27bCfDce5E276d92Eb563bb73C",
  "rate": 1000, "decimals": 18, "symbol": "HARTII" }

The legacy fields above are unchanged, and the response is still cached with Cache-Control: public, max-age=60. Since 1.19.0 it also carries an hbome block, written from source (functions/api/token/config.js and functions/api/_lib/hbome.js):

json
{
  "hbome": {
    "token": "0x001fA4fdb848BB8C98B0d6Cd85257EfcCAe3b425",
    "symbol": "HBOME",
    "decimals": 18,
    "marketplace": null,
    "getUrl": "https://hartiilabs.com/token/0x001fa4fdb848bb8c98b0d6cd85257efccae3b425"
  }
}
FieldMeaning
token, symbol, decimalsThe HBOME token (HartiiBiomeInc), "HBOME" and 18. Checksum token with quais.getAddress(token.toLowerCase()) before you pass it to an SDK.
marketplaceThe HBOME marketplace that Shop checkout runs through, or null. It stays null until the HBOME_MARKETPLACE setting holds a well-formed address, and the Shop keeps Buy disabled while it is null. No marketplace address is set in 1.19.0.
getUrlWhere a player gets HBOME: the token's page on HartiiLabs.

GET /api/token/hbome/:wallet

New in 1.19.0. The wallet's HBOME balance. It feeds the HBOME chip in the top bar and the balance in the Shop and Me. The call is read-only. The server makes an ERC-20 balanceOf call on the HBOME token through the shared Quai read RPC (the gateway first, QuaiScan as the fallback). It holds no keys and sends nothing.

bash
curl -s https://hartiigames.com/api/token/hbome/<wallet>

A 200 response, written from source (functions/api/token/hbome/[wallet].js):

json
{
  "token": "0x001fA4fdb848BB8C98B0d6Cd85257EfcCAe3b425",
  "symbol": "HBOME",
  "wei": "25500000000000000000",
  "decimals": 18,
  "hbome": 25.5,
  "available": true
}
FieldMeaning
tokenThe HBOME token contract. Checksum it with quais.getAddress(token.toLowerCase()) before you pass it to an SDK.
symbol"HBOME".
weiThe exact balance in the token's smallest unit, as a decimal string. Use it for any arithmetic.
decimalsAlways 18. The endpoint does not read decimals() from the chain: HBOME's decimals are fixed, so a request costs one RPC call, not two.
hbomewei divided by 10 ** decimals, as a JSON number. It is for display and loses precision on very large balances.
availabletrue on every 200.

A wallet with no HBOME gets "wei": "0" and "hbome": 0.

Caching and errors:

  • The response is Cache-Control: no-store, so a browser never keeps it. The server keeps each wallet's answer for 10 seconds per edge location, so two requests inside that window can return the same balance. Add ?fresh=1 to skip the stored copy and read the chain (the Shop does this right after a purchase).
  • 400 with { "error": "bad wallet" } when :wallet is not 0x followed by 40 hex digits. Any letter case is accepted.
  • 502 with exactly { "error": "balance read failed" } when the RPC read fails. An empty or short answer (anything that is not a full 32-byte word) is also a 502. The detail goes to the server log, not the response. A failed read is a 502, never a zero.
  • No CORS headers are set, so a browser page on another origin cannot read it. Call it from a server.

Identity

  • GET /api/username/:wallet → { wallet, name }, CORS-open, name: null if 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:

json
{ "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 and auth/nonce.js are internal plumbing / operator-only — no public contract documented here beyond "exists and is gated." daily/nonce (the nonce for a wallet's signed daily submission) is not documented here either.

Biome endpoints

Base: https://hartiibiome.com. The routes below are GET, read-only, no wallet/money logic. The only other route on this list is the analytics page's own counting beacon, POST /api/t (see analytics traffic).

GET /api/chain

json
{ "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

json
{ "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. While paid play is off, Games' monthly standings (/api/tournament) count each game's current-season free board only, so scores from different rules never mix. gamesWithPlay counts the roster games that have at least one score this month, so it is at most eleven since 1.19.0.

json
{ "games": { "month": "202609", "gamesWithPlay": 10,
  "standings": [{ "wallet": "0x00700bd1…", "points": 5772, "games": 6, "name": "Swimgood" }] } }

GET /api/analytics

Added in 1.22.0. One cached answer for the analytics page: Labs, Games, Gallery, the two buyback ledgers, the contract counters and the event-log counts. It is read server-side because Games' API is closed to browsers and the log counts need many paged calls to Quaiscan. It is cached 5 minutes per colo when every source answered (x-analytics-complete: 1). A partial answer is kept for 30 seconds only, so a failing source is asked once and not once per visitor.

json
{ "at": 1791571072164, "complete": true,
  "sources": { "labsStats": { "ok": true, "at": 1791571069162, "status": "ok" },
               "ledgerHbome": { "ok": false, "at": 1791571069244, "status": "not_published" }, "…": "…" },
  "labs": { "launched": 90, "active": 85, "graduated": 5, "holders": 300, "isPartial": false,
            "volume24hWei": "1000000000000000000000", "volume7dWei": "…", "volumeAllWei": "…",
            "burns": [{ "address": "0x…", "symbol": "ABC", "burnedWei": "1000000000000000000" }] },
  "games": { "version": "1.22.0", "healthy": true, "month": "202610", "gamesWithPlay": 0, "paidPlay": false },
  "gallery": { "collections": 45, "minted": 5000, "mintedUnknown": 19, "creators": 6, "creatorsUnknown": 30,
               "activeListings": 10, "partial": false, "version": "…", "healthy": true },
  "ledgers": { "qaxe": { "asOf": "2026-09-17T16:37:55.189Z", "tokensBurned": 1000, "burnCount": 2, "purchases": 3,
                         "lastBuybackAt": "2026-09-17T16:37:55.189Z" },
               "hbome": null },
  "tools": { "profiles": { "names": 10 },
             "claim": { "transactions": 18, "campaigns": 2, "claims": 14, "atLeast": false },
             "otc": { "transactions": 6, "offers": 3, "filled": 2, "cancelled": 1, "atLeast": false },
             "airdrop": { "transactions": 4, "batches": 4, "recipients": 80, "atLeast": false } } }

The numbers above are illustrative, not a live reading. null means unknown, never 0. Every source reports { ok, at, status }, where status is ok, not_published (the HBOME buyback ledger does not exist yet, which is expected and still counts as a complete answer) or error. Wei values are strings. A group is null when every source behind it failed.

  • gallery.minted and gallery.creators add up only the collections that report a count or a creator, and mintedUnknown and creatorsUnknown say how many do not. A non-zero value means the figure is a floor. Both figures are null when no collection reports one.
  • labs.burns has one row per token. Labs' cross-token burn total is not passed on. Token symbols are cleaned: control characters, direction marks and zero-width marks are removed and a symbol is cut to 32 characters.
  • tools.*.atLeast: true means the log list reached its page cap (20 pages) or its 8 second deadline, so the count is a lower bound. tools.*.transactions is Quaiscan's all-time transaction counter for the contract and counts every call.
  • ledgers.hbome holds, when the file is published, asOf, tokensAcquired, treasuryFundedTokensAcquired, feeFundedTokensAcquired, tokensBurned, pendingBurn, burnCount, purchases, ownerBurned and ownerBurnedFromBuybacks. It is null until then. See the note under Burns.

GET /api/analytics/traffic

Added in 1.22.0. Aggregates of the visits counted by this site (see Privacy), never a single visit. The query is ?range=24h|7d|30d: range is one of the three values, and anything else is read as 7d. It is cached 5 minutes per colo when every query answered (x-traffic-complete: 1) and 30 seconds when one failed.

json
{ "ok": true, "counting": true, "range": "7d", "at": 1791571072164, "views": 120, "visitorDays": 45, "visitorDaysAtLeast": false,
  "pages": [{ "name": "/", "n": 50 }],
  "referrers": [{ "name": "Direct or hidden", "n": 20 }, { "name": "Other (under 3)", "n": 2 }],
  "countries": [{ "name": "US", "n": 30 }], "devices": [{ "name": "mobile", "n": 70 }],
  "series": [{ "day": "2026-10-07", "n": 40 }],
  "events": { "hpay_link_created": 7, "hpay_send_confirmed": 3, "claim_confirmed": 2, "search_address": 9 },
  "toolViews": { "/hpay": 40, "/claim": 12 } }

The numbers above are illustrative, not a live reading. While counting is switched off, which is how it ships, the answer is { "ok": false, "reason": "counting_off" }. With counting on but the read credentials not set it is { "ok": false, "reason": "not_configured" }, and if no query could be answered { "ok": false, "reason": "unavailable" }. All three come with Cache-Control: no-store. The reader asks the same switch the beacon does, so a credential set early cannot make an empty or old dataset look live.

  • Countries and referrers with fewer than 3 visits are folded into Other (under 3). A visit with no referrer is Direct or hidden, an unknown country is Unknown, and a visit from one page of this site to another is not a referrer. The lists hold at most 10 pages, 25 referrers, 25 countries and 10 device kinds.
  • visitorDays is the sum over days of distinct daily visitor codes. visitorDaysAtLeast is true when some views carried no code. visitorDays is null when views exist and none carried a code, and 0 only when there are no views.
  • series[].day is a UTC calendar day such as 2026-10-07, or the start of an hour such as 2026-10-07 14:00 when the range is 24h. A day or hour between the first and last counted one with no views is 0.
  • events lists every action name with 0 where none was counted (hpay_link_created, hpay_send_confirmed, claim_link_opened, claim_confirmed, otc_offer_created, otc_offer_filled, airdrop_batch_sent, search_block, search_hash, search_address), and toolViews lists the page views of /hpay, /claim, /otc and /airdrop. A part that could not be read is null.

POST /api/t

The page's own counting beacon. It is not an integration point and takes no input you should build on. It always answers 204 with an empty body, whatever happened, and counts nothing while counting is switched off. It stores only what Privacy lists.

Quai Network mainnet · chain 9 · Cyprus-1. Figures marked "read on" a date were read from the chain that day; re-read before relying on them.