Archive API
Hyperliquid purges a HIP-4 market shortly after it settles: its coin, its candles, its order book and its settlement votes disappear from the node API. Hypersight indexes that data while the market is live and preserves it after resolution, and this API exposes the full archive, publicly and for free.
https://api.hypersight.xyz/v1- No authentication. Every endpoint is a plain
GET, with no query parameters: each resource is one whole document.HEADworks too (handy for ETags); anything else answers405. - CORS is open (
*): call it straight from a browser app. - Rate limit: 300 requests per minute per IP. Over it you get a
429withRetry-After: 60and anX-RateLimit-Limitheader, so a client can back off on its own. If you need more, reach out. - JSON only, served from pre-computed snapshots on Cloudflare’s edge.
Ordering, because it differs by resource. archive.json is sorted by
expiryMs descending and events.json by settledAt descending, so
the newest resolution comes first in both. A market’s odds array runs the
other way, ascending by ts, because it is a time series. Both are
stable: if you ingest incrementally, read the aggregates until you meet an id
you already hold.
Quickstart
# What's in the archive right now
curl https://api.hypersight.xyz/v1/manifest.json
# Every settled price market (BTC/ETH/SOL/HYPE binaries & buckets)
curl https://api.hypersight.xyz/v1/archive.json
# One market: result, final score, traded volume, settlement verdict
curl https://api.hypersight.xyz/v1/markets/813.json
# Its odds curve, in a separate file
curl https://api.hypersight.xyz/v1/markets/813/odds.jsonThe whole path, once
Every other page describes one resource. This is how they connect: watch what settles, open a market, and pull only the pieces you actually want. It is the loop a real integration runs.
import requests
API = "https://api.hypersight.xyz/v1"
get = lambda p: requests.get(API + p, timeout=30).json()
# 1. What has resolved lately. Poll THIS, not the aggregates — it is 44 KB
# against 658 KB, and it tells you exactly which ids moved.
for change in get("/changes.json")["changes"][:5]:
oid = change["outcomeId"]
# 2. The record itself. ~1.5 KB: the question, the winner, the volume,
# the deployer, and pointers to everything heavy.
m = get(f"/markets/{oid}.json")
row = m["market"]
print(f"#{oid} {row.get('questionTitle') or row['underlying']} "
f"-> {row.get('winnerLabel') or row['result']} ${row['totalVolume']:,.0f}")
# 3. Traded volume is not total volume. `totalVolume` also carries
# settlement payouts, merges and mints — across the archive that is 41%
# of it. buy + sell is what changed hands.
f = m.get("flow") or {}
if f.get("buyNotional") is not None:
print(f" traded ${f['buyNotional'] + f['sellNotional']:,.0f} "
f"in {f.get('tradeFills')} trades by {f['traders']} wallets")
# 4. The curve, only if you want it — check the size first. `points` runs
# from 2 to 145,048, which is 4.8 MB.
if m["oddsUrl"] and m["oddsMeta"]["points"] > 100:
odds = get(m["oddsUrl"])["odds"]
print(f" {len(odds)} points, opened at {odds[0]['px']:.2f}, "
f"settled at {odds[-1]['px']:.2f}")
# 5. Who voted it, when there was a vote. Price markets settle from the
# oracle and never carry one — that is `settlementApplicable`, not a gap.
if m.get("settlementUrl"):
voters = get(m["settlementUrl"])["voters"]
print(f" settled by {len(voters)} validators: {m['settlement']['reason']}")Four rules are worth taking from that loop:
- Poll
changes.json, never the aggregates. It names the ids that moved and costs 44 KB against 658 KB. - The market document is the index, not the payload. Everything large sits behind a URL it hands you: the odds curve, the underlying’s price, the settlement roster.
- Check
oddsMeta.pointsbefore fetching a curve. That field exists so the decision is yours rather than a surprise. totalVolumeis not what was traded. AddbuyNotionalandsellNotionalfor that; the difference is the protocol paying out, not the market changing hands. Both fields are on everyarchive.jsonandevents.jsonrow too, so a volume study never needs to open 1,066 market documents. And add them — never subtractsplitNotionalfrom the total, which is a residual between two instruments and returns a non-zero figure on markets nobody has ever traded.
The first call answers with the archive’s table of contents, which is the shortest way to see the shape of things:
{
"generatedAt": 1787000000000,
"freshness": "regenerated on market settlement (~1 min) and nightly; not continuous",
"license": {
"name": "CC BY 4.0",
"url": "https://creativecommons.org/licenses/by/4.0/",
"attribution": "Hypersight (hypersight.xyz)"
},
"coverage": { "firstExpiryMs": 1777788000000, "lastExpiryMs": 1787000000000 },
"counts": { "settledMarkets": 700, "settledEvents": 300, "marketFiles": 1000 },
"resources": ["/v1/archive.json", "/v1/events.json", "…"]
}Counts and timestamps are illustrative — the live manifest is the only truthful copy of its own numbers.
The root (https://api.hypersight.xyz/) also serves the manifest.
In your language
Pulling one market, its odds curve and its settlement voters:
const BASE = "https://api.hypersight.xyz/v1";
const market = await fetch(`${BASE}/markets/813.json`).then((r) => r.json());
console.log(market.market.outcomeLabel, "won:", market.market.winnerLabel);
// The curve is a separate file, and only exists when something was captured.
if (market.oddsUrl) {
const { odds } = await fetch(`https://api.hypersight.xyz${market.oddsUrl}`).then((r) => r.json());
console.log(odds.length, "points, first:", new Date(odds[0].ts), odds[0].px);
}
// Price markets settle from the oracle: settlementApplicable is false there,
// and the false next to it is not a gap in our capture.
if (market.settlementApplicable && market.settlementCaptured) {
console.log(market.settlement.voters.length, "validators voted");
}
// Fills-derived: distinct traders, real fees in USD, buy/sell/mint split.
// Null until the fills sweep covered the market; never zero.
if (market.flow) {
console.log(market.flow.traders, "traders,", market.flow.fills, "trades, fees $" + market.flow.fee.toFixed(2));
}
// TRADE odds: every real transaction price, based on the fills. Exists even
// for markets that settled before the mid capture was born.
if (market.tradeOddsUrl) {
const { odds } = await fetch(`https://api.hypersight.xyz${market.tradeOddsUrl}`).then((r) => r.json());
console.log(odds.length, "trades plotted");
}Ingesting the whole archive is two calls, archive.json and events.json,
plus one per market whose curve or voter set you want.
Freshness
The API serves static snapshots, not live queries. Snapshots are
regenerated when a market settles (within about a minute) and once a night
in full. Every response carries a generatedAt timestamp (epoch ms): read it
rather than assuming real time.
Edge caching adds a little on top: aggregate files can lag a few minutes after a settlement, and an already-cached per-market file up to an hour. A freshly settled market’s file is generated new, so it is fresh from its first read.
This is a deliberately modest guarantee: the archive is immutable data about
resolved markets, not a live feed. For live prices and order books, use
Hyperliquid’s own /info API.
Stability
The v1 schema is stable. Changes are additive only: new fields may
appear, existing fields will not change meaning or disappear. A breaking change
would ship as a new version prefix, with v1 kept serving.
Availability is a different promise, and the honest one is smaller: this runs best-effort, with no uptime guarantee and no SLA. It is maintained by an independent team as a public good for the Hyperliquid ecosystem. Cache what you depend on.
Using the data
The data is licensed CC BY 4.0:
use it freely, including commercially — copy it, reshape it, build products on
it — with attribution. Credit “Hypersight” with a link to
hypersight.xyz (or this documentation) wherever the
data appears. The licence is also declared machine-readably in
manifest.json and in
the OpenAPI document.
No key, no quota beyond the rate limit.
If you are building something that needs more than this serves, say so: the gaps other people hit are the best guide to what to index next.
What’s inside
| Resource | What it holds |
|---|---|
/v1/archive.json | Every settled price market: target, settle price, result, volume, distinct traders |
/v1/events.json | Every resolved event market: sides, winner, final score, question rules, distinct traders |
/v1/governance.json | Validator settlement activity, aggregated |
/v1/markets/{id}.json | One market: result, settlement voters, flow (fills-derived: trades, traders, real fees in USD, buy/sell/mint split, settlement tail), dataQuality, the underlying’s price series |
/v1/markets/{id}/odds.json | The MID odds curve (order-book mid), kept separate because it can be large |
/v1/markets/{id}/trade-odds.json | The TRADE odds curve — every real transaction price from the fills archive. Exists even for markets that settled before the mid capture was born |
/v1/archive/{YYYY-MM}.json | One month’s slice, for fetching or backfilling part of the archive |
/v1/changes.json | Recent resolutions, newest first: poll this instead of re-reading everything |
/v1/bulk/manifest.json | Gzipped JSON Lines of the whole archive, to ingest it once |
/v1/openapi.json | OpenAPI 3.1 description, field by field, if you generate clients |
/v1/manifest.json | Counts, coverage window, shard index, freshness contract |
Polling without waste
Every response carries an ETag. Send it back as If-None-Match and an
unchanged artefact answers 304 with no body, so a poller costs almost
nothing:
ETAG=$(curl -sI https://api.hypersight.xyz/v1/changes.json | grep -i '^etag:' | cut -d' ' -f2)
curl -s -o /dev/null -w '%{http_code}\n' -H "If-None-Match: $ETAG" \
https://api.hypersight.xyz/v1/changes.json # 304This works from a browser too: ETag, Retry-After and X-RateLimit-Limit
are CORS-exposed, so res.headers.get("etag") reads them cross-origin instead
of returning null.
The pattern that scales: ingest bulk/ once, then poll changes.json with
an ETag and fetch only the markets it names.
Continue with the resource reference, or read coverage & completeness to understand exactly what the data does and does not claim.