Hyperliquid trades and fills data: API, archive and history by wallet
Hyperliquid records every match as two fills, one per side. This page covers what each fill holds, what the public API returns and where it stops, where older fills live, and how to pull tick data by market or by wallet over REST.
The short version#
- Each trade is two fills. They share a
tid. Count trades and volume once pertid. - By wallet, recent:
userFillsanduserFillsByTime. Capped at the 10000 most recent fills, and they leave out TWAP slices. - By market:
recentTradesreturns only the latest trades. There is no public trades history request. - Everything older:
node_fills_by_blockins3://hl-mainnet-node-data, or your own node. - Over REST with history: Ironflow
/v1/tradesby market and/v1/fillsby address.
1. Fills and trades#
A trade is one match between a buyer and a seller. Hyperliquid writes it as two fills: one for the buy side, one for the sell side. Both carry the same tid, price, size and time. Each fill has its own address, fee, closed PnL and direction. The side that crossed the book pays the taker fee. The resting side pays the maker fee or gets a rebate.
In a 5 minute BTC window on 27 October 2026 we pulled 984 fills. They held 492 distinct tidvalues, and 492 was the trade count in Hyperliquid's own 1m candles for those minutes. Summing size over all 984 rows gives twice the traded volume.
2. Fill fields#
These are the fields of a fill object in the API and in node files. The last column is the name in Ironflow's REST responses.
| Field | Meaning | Ironflow |
|---|---|---|
| coin | Market. BTC for a native perp, xyz:NVDA for a HIP-3 market. | market (BTC-PERP, xyz:NVDA-PERP) |
| px, sz | Price and size as decimal strings. Size is in the base asset. | price, size |
| side | B for the buy side, A for the sell side. | side (buy, sell) |
| time | Unix milliseconds. | timestamp |
| tid | Trade id. Both sides of one trade carry the same tid. | tid |
| oid, cloid | Order id, and the client order id when the trader set one. | oid |
| crossed | true when this side took liquidity. | not returned |
| fee, feeToken | Fee paid. A negative fee is a maker rebate. | fee, fee_token |
| closedPnl | PnL realized by this fill. | realized_pnl |
| dir, startPosition | Open Long, Close Short, Short > Long and so on, and the position before the fill. | not returned |
| hash | Transaction hash. In our samples TWAP slice fills had an all-zero hash. | hash (empty in current responses) |
| twapId | Set on TWAP slice fills. | not returned |
| builderFee, deployerFee | Node files and the S3 archive: builderFee when the order used a builder code, deployerFee on HIP-3 markets. userFills left deployerFee out in our checks. | not returned |
| liquidation | Present on liquidation fills: liquidatedUser, markPx, method. | see /v1/liquidations |
Ironflow rows add block_number and sequence, the fill's order inside its block, plus notional, display_symbol, market_class and quote_asset.
3. The Hyperliquid API#
All of these go to POST https://api.hyperliquid.xyz/info. Limits are from the info endpoint docs, checked on 27 October 2026.
| Request | What it returns |
|---|---|
| userFills | One address. At most 2000 most recent fills. No time range. |
| userFillsByTime | One address and a time range. At most 2000 fills per response. Only the 10000 most recent fills are available. |
| userTwapSliceFills | One address. TWAP slice fills, which the two requests above leave out. At most 2000 most recent. |
| recentTrades | One coin. The latest trades only. No time range. Our call for BTC returned 10. |
# fills for one address since a time, oldest first, 2000 per response
curl -s https://api.hyperliquid.xyz/info \
-H 'Content-Type: application/json' \
-d '{"type":"userFillsByTime","user":"0xYOUR_ADDRESS","startTime":1792972800000}'
# latest trades for one coin: coin, side, px, sz, time, hash, tid, users
curl -s https://api.hyperliquid.xyz/info \
-H 'Content-Type: application/json' \
-d '{"type":"recentTrades","coin":"BTC"}'The 10000 fill cap#
userFillsByTime returns fills oldest first. Page forward by setting startTimeto the last fill's time. For a busy market maker address on 27 October 2026, a request from startTime: 0 started 33 hours back. Everything before that was gone from the API.
TWAP slices#
userFills and userFillsByTime leave out TWAP slice fills. We checked an address that was running a BTC TWAP: userFillsByTime returned nothing for a window where it had slice fills every 30 seconds. userTwapSliceFills returned them. Node files and the S3 archive include TWAP slices with a twapId.
userFills, userFillsByTime and recentTrades add rate limit weight for every 20 items returned. The rate limits guide has the numbers. The WebSocket userFills subscription streams new fills, with a cap of 10 unique users per IP.
4. The S3 fills archive#
Hyperliquid publishes every fill in s3://hl-mainnet-node-data/node_fills_by_block, one hourly LZ4 file per hour. The bucket is requester-pays: you need an AWS account and you pay for transfer. When we listed it, this prefix started in late July 2025. Older periods are in node_fills, which matches the API format, and node_trades, which does not. The historical data guide lists every bucket.
aws s3 cp s3://hl-mainnet-node-data/node_fills_by_block/hourly/20260918/9.lz4 . \
--request-payer requester
unlz4 --rm 9.lz4
# keep one wallet's fills from that hour (addresses are lowercase in these files)
jq -c --arg a 0xyour_address '.events[] | select(.[0] == $a) | .[1]' 9 > wallet.jsonlTo rebuild a wallet's full history from the archive you download every hour in the range and filter each one. The files hold every address, so most of what you download is other people's fills.
5. What a node writes#
A non-validator node started with --write-fills --batch-by-block writes the same files as blocks arrive, under ~/hl/data/node_fills_by_block/hourly/. Each line is one block. events is a list of [address, fill] pairs in execution order. This block from the archive holds one BTC trade:
{"local_time": "2026-03-22T01:00:00.425752239",
"block_time": "2026-03-22T01:00:00.114473824",
"block_number": 931271363,
"events": [
["0xb3a38662575bdf1541013ce987934dba919851ea",
{"coin": "BTC", "px": "68895.0", "sz": "0.00184", "side": "B",
"time": 1774141200114, "dir": "Close Short", "closedPnl": "-0.011224",
"crossed": true, "fee": "0.018254", "tid": 99990795293934, "twapId": null}],
["0xb668858ff53322d5511651a2cd92391eb5d60ee3",
{"coin": "BTC", "px": "68895.0", "sz": "0.00184", "side": "A",
"time": 1774141200114, "dir": "Close Long", "closedPnl": "0.137632",
"crossed": false, "fee": "-0.003803", "tid": 99990795293934, "twapId": null}]]}Some fields are trimmed above. Node fills also carry the builder address of the order. The public API shows builderFee but not that address. A node does not backfill: it writes from the block where it synced. The node guide covers flags, disk use and cleanup.
6. Ironflow /v1/trades and /v1/fills#
Ironflow indexes these fills from its own nodes and serves them at https://api.ironflow.sh. Both endpoints answer without a key. /v1/trades returns every fill in a market, both sides. /v1/fills returns one address's fills and requires address.
# newest BTC fills, both sides of each trade
curl "https://api.ironflow.sh/v1/trades?market=BTC-PERP&order=desc&limit=3"
# one wallet's fills, any market
curl "https://api.ironflow.sh/v1/fills?address=0xYOUR_ADDRESS&limit=100"Free keyWithout a key the API reaches 24 hours back. A free key reaches 30 days at 60 requests a minute. Get one by email.
One row from the first call, with the repeated venue fields trimmed:
{
"market": "BTC-PERP",
"market_type": "perp",
"timestamp": 1793089259026,
"block_number": 1198922811,
"sequence": 7,
"price": "79855",
"size": "0.00334",
"side": "buy",
"address": "0xd3bbe70c4cc5d24637aabbb768a626e38a8c9435",
"fee": "0.120022",
"realized_pnl": "0",
"notional": "266.7157",
"oid": 587071244104,
"tid": 822197913620469,
"fee_token": "USDC"
}- Parameters:
market,fromandtoin Unix milliseconds,limitup to 1000,cursor./v1/tradesalso takesorder=asc|desc. - Paging: pass
pagination.next_cursorback ascursoruntilhas_moreis false. - TWAP slices are in
/v1/fillswith the other fills. The address we checked above showed its slice fills there. - Window per request: 1 hour without a key, 6 hours with a free key. A wider range returns 400
QUERY_WINDOW_EXCEEDED. Split it into smaller windows. - History: 24 hours without a key, 30 days with a free key, up to 12 months on paid plans.
- Rate: 10 requests per minute per IP without a key, 60 with a free key. Over that you get 429
RATE_LIMIT_EXCEEDED.
7. Python: one window of tick data#
This pulls every BTC fill in a 5 minute window, follows the cursor, then counts trades and volume once per trade. It runs without a key. Set IRONFLOW_API_KEY for 30 days of history.
import os
import time
import requests
headers = {}
if os.getenv("IRONFLOW_API_KEY"):
headers["Authorization"] = f"Bearer {os.environ['IRONFLOW_API_KEY']}"
to = int(time.time() * 1000) // 60_000 * 60_000
params = {"market": "BTC-PERP", "from": to - 5 * 60_000, "to": to, "limit": 1000}
rows = []
while True:
page = requests.get("https://api.ironflow.sh/v1/trades", params=params, headers=headers).json()
rows += page["data"]
if not page["pagination"]["has_more"]:
break
params["cursor"] = page["pagination"]["next_cursor"]
trades = {r["tid"] for r in rows} # two rows per trade, one per side
volume = sum(float(r["size"]) for r in rows if r["side"] == "buy")
print(len(rows), "fills,", len(trades), "trades,", round(volume, 5), "BTC")The same pattern against the Hyperliquid API, for one wallet. It stops at the 10000 fill cap without saying so, and it misses TWAP slices.
import requests
def user_fills(address, start_ms=0):
fills, seen = [], set()
while True:
batch = requests.post(
"https://api.hyperliquid.xyz/info",
json={"type": "userFillsByTime", "user": address, "startTime": start_ms},
).json()
new = [f for f in batch if (f["tid"], f["oid"]) not in seen]
if not new:
return fills
fills += new
seen.update((f["tid"], f["oid"]) for f in new)
if len(batch) < 2000:
return fills
start_ms = batch[-1]["time"] # fills that share this time are deduped aboveQuestions#
How do I get all historical fills for a Hyperliquid wallet?#
The API keeps only the 10000 most recent fills per address. Older fills are in node_fills_by_block in the public S3 archive: download the hours you need and keep the rows for that address. Ironflow's /v1/fills returns them by address over REST, as far back as your access level allows.
What is the difference between a fill and a trade on Hyperliquid?#
A trade is one match between a buyer and a seller. It produces two fills, one per side, with the same tid. A fill carries one account's fee, closed PnL and direction.
Why are TWAP fills missing from userFills?#
userFills and userFillsByTime leave out TWAP slice fills. Request them with userTwapSliceFills. Node output and the S3 archive include them with a twapId.
Where can I get Hyperliquid tick data?#
Every fill is in node_fills_by_block, from the S3 archive or from your own node. Ironflow serves the same fills by market at /v1/trades, with block number and in-block sequence on each row.
Does the Hyperliquid trades API return history?#
No. recentTrades returns only the latest trades for one coin and takes no time range. For a past window, use the S3 archive, a node, or an indexed API such as /v1/trades.
Why is my Hyperliquid volume twice the real number?#
You summed both fills of every trade. Count each tid once, or sum only the buy side.
Related guides#
- Hyperliquid historical data: API, S3 archive and Python
- Hyperliquid candles and OHLCV data
- Hyperliquid API rate limits explained
- How to run a Hyperliquid non-validator node
References#
- Hyperliquid docs: Info endpoint. userFills, userFillsByTime, userTwapSliceFills, recentTrades and their limits.
- Hyperliquid docs: Historical data. The S3 buckets and the requester-pays rule.
- Hyperliquid docs: L1 data schemas. Field layout of node_fills_by_block.
- hyperliquid-dex/node. The --write-fills and --batch-by-block flags.
Every fill above is queryable by market or wallet over REST. The API page has the endpoints and an MCP server for AI clients. A free key gives 30 days of history. For reads at node scale, see the nodes page.