Recipe 64 of 65
Load a month of Hyperliquid fills into Polars or Postgres within the daily quota
Read the month's file sizes from /archive/datasets, then for each day ask /archive/url for a link right before downloading, keep the rows you need and delete the raw file; a month of fills is about 9 to 11 GB against an allowance of 20 GB of links per 24 hours.
GET /v1/hyperliquid/archive/datasets and GET /v1/hyperliquid/archive/url answer it together. A month of fills is
30 daily Parquet files. Budget first: the index lists every day's bytes, and the sum
must fit the allowance of 20 GB of links per user per rolling 24 hours. Then loop over the days: ask
/archive/url for one link, download the file at once (the link lasts 300 seconds), keep the rows you need, and delete
the raw file before the next day. The full parameter lists are on
Archive file index and Archive download link.
Quick answer
Read the month's size first, then run the download loop.
# days and GB of the month's fills files
curl -s -H "X-API-Key: $VIKE_API_KEY" \
"https://data.vike.io/v1/hyperliquid/archive/datasets" \
| jq '[.dates[] | select(.date | startswith("2026-09")) | .streams.fills.bytes] | {days: length, gb: (add / 1e9)}'import os
from pathlib import Path
import polars as pl
import requests
API = "https://data.vike.io/v1/hyperliquid"
H = {"X-API-Key": os.environ["VIKE_API_KEY"]}
MONTH = "2026-09"
index = requests.get(f"{API}/archive/datasets", headers=H, timeout=60).json()
days = sorted(d["date"] for d in index["dates"] if d["date"].startswith(MONTH) and "fills" in d["streams"])
need = sum(d["streams"]["fills"]["bytes"] for d in index["dates"] if d["date"] in days)
print(len(days), "days,", round(need / 1e9, 2), "GB of links") # must fit 20 GB per rolling 24 hours
for day in days:
out = Path(f"btc-{day}.parquet")
if out.exists():
continue # done on an earlier run: no new link is asked for
ask = requests.get(f"{API}/archive/url", params={"dataset": "fills", "date": day}, headers=H, timeout=60)
ask.raise_for_status() # a 429 here says how much was issued in the last 24 hours
raw = Path(f"fills-{day}.parquet")
with requests.get(ask.json()["url"], stream=True, timeout=(10, 300)) as r: # the link needs no key
r.raise_for_status()
with raw.open("wb") as f:
for chunk in r.iter_content(1 << 20):
f.write(chunk)
pl.scan_parquet(raw).filter(pl.col("asset") == "BTC").sink_parquet(out) # keep only what you need
raw.unlink() # free the disk before the next day
month = pl.read_parquet(f"btc-{MONTH}-*.parquet")Example response
Captured from the live API on 2026-10-24 at 17:17 UTC: one link from /archive/url. The signed token in url is replaced, because a live link
is a credential for 300 seconds.
bytes is the file's size, the amount this link counts against the allowance; rows its fills. On the same day's
index, September 2026 was 30 files and 9.09 GB, the 30 days to 2026-10-23 were 10.54 GB, and the largest single day
(2026-10-15) was 0.54 GB.
{
"url": "https://data.vike.io/dl/<exp>.<kid>.<sig>/venue=hyperliquid/date=2026-10-23/fills.parquet",
"expires_at": "2026-10-24T17:22:08Z",
"expires_in": 300,
"dataset": "fills",
"date": "2026-10-23",
"bytes": 357514873,
"rows": 11936174
}Variations
Into Postgres. Write each kept day to CSV and copy it in. Keep trade_id and order_id as numeric(20,0): they are unsigned 64-bit
and some trade ids are above the signed bigint range.
pl.read_parquet("btc-2026-09-01.parquet").write_csv("btc-2026-09-01.csv", datetime_format="%Y-%m-%dT%H:%M:%S%.3f%:z")CREATE TABLE hl_fills (
fill_ts timestamptz, wallet text, asset text, side text,
size_token double precision, price double precision, size_usd double precision,
fee double precision, fee_token text, is_maker boolean,
order_id numeric(20,0), trade_id numeric(20,0), crossed boolean,
closed_pnl double precision, dex_name text, start_position double precision
);
\copy hl_fills FROM 'btc-2026-09-01.csv' WITH (FORMAT csv, HEADER true)The columns are in file order, so the CSV header lines up with the table. An empty start_position (fills before
2026-09-29) loads as NULL.
One link and download in one step. redirect=1 answers 302 with the link in Location; -L follows it.
# one day of fills straight to disk
curl -s -L -H "X-API-Key: $VIKE_API_KEY" -o fills-2026-09-01.parquet \
"https://data.vike.io/v1/hyperliquid/archive/url?dataset=fills&date=2026-09-01&redirect=1"Try the code with no key first. The free sample has the same 16 columns; run the filter and the Postgres load on it before spending allowance.
# one BTC hour of fills, no API key
curl -o fills-sample.parquet "https://data.vike.io/archive/hl-samples/fills-sample.parquet"Before you rely on it
The allowance counts links, not downloads
Every link issued counts its file's full bytes for 24 hours, whether you download it or not, and all of a user's keys
share one allowance. A link asked for and left unused still counts, so ask for each link right before its download, and
let a rerun skip the days already kept, as the loop does. Past the allowance the call answers 429 with no
Retry-After; ask again once earlier links have aged out.
Download whole files; one file is one UTC day
A link is accepted for 300 seconds from the start of each request, so a reader that opens new requests over HTTP fails
part way; download the whole file, then read it locally. The archive starts on 2026-05-01 and a day appears in the
early UTC morning after it ends. Both sides of every trade are in each file: filter is_maker = false before summing
notional.
Ask your agent
No MCP tool reads the archive: use the files.