Why Your Hyperliquid Bot Hits the Rate Limit
The hyperliquid api rate limit 429 most bots hit is not a per-request cap. Hyperliquid's REST endpoints share one aggregated weight budget of 1,200 per minute per IP, and every /info request type has its own weight. A naive candle poller that loops over coins and calls candleSnapshot for each spends that budget faster than it refills, and once it is gone every request from that IP is refused until the window rolls.
The fix is not a sleep. It is knowing what each request costs, and then not paying for the same bars twice. This post covers the weight table from Hyperliquid's rate-limits page, the arithmetic for a typical scanner, and the cached batch route at /api/v1/hyperliquid/candles/batch that returns up to 25 coins in one call.
What Does Each Hyperliquid Info Request Cost?
These are the weights Hyperliquid publishes for its REST API (checked October 2026). The exchange shares one budget across all of them: 1,200 weight per minute per IP.
| Request | Weight | Notes |
|---|---|---|
l2Book, allMids, clearinghouseState, orderStatus, spotClearinghouseState, exchangeStatus | 2 | The cheap reads |
candleSnapshot | 20 | Plus an additional weight per 60 candles returned |
fundingHistory, userFills, recentTrades, historicalOrders and similar list reads | 20 | Plus an additional weight per 20 items returned |
userRole | 60 | The most expensive info request |
Every other documented /info type | 20 | The default |
| Exchange actions (orders, cancels) | 1 + floor(batch_length / 40) | An unbatched action is 1 |
| Explorer requests | 40 | blockList adds 1 per block |
Two things in that table surprise people. First, allMids and l2Book are ten times cheaper than a candle pull, so a price-only loop is fine and a candle loop is not. Second, the per-item surcharges mean a 1,000-bar candleSnapshot costs more than a 100-bar one, which matters for a backfill.
Actions also carry an address-based limit that info requests do not: one request per 1 USDC of cumulative volume, with a 10,000-request starting buffer, and one request every 10 seconds once that is exhausted. WebSockets have their own caps (10 connections, 1,000 subscriptions, 2,000 messages sent per minute). The docs do not describe the error body, but in practice the REST API answers an exhausted budget with HTTP 429.
How Fast Does a Candle Poller Burn 1,200 Weight?
Take a scanner that watches 25 perps on the 1h chart and refreshes every 30 seconds. Each refresh is 25 candleSnapshot calls at 20 weight, so 500 weight before the per-60-bar surcharge. Two refreshes a minute is 1,000 weight. Add a few metaAndAssetCtxs reads for funding and open interest at 20 each and the budget is gone. The third sweep in that minute fails.
Now the same scanner on 50 coins: a single sweep is 1,000 weight. You get one refresh a minute at best, and nothing else from that IP. The usual symptoms are:
- A burst of 429s at the top of each minute, then a quiet period, then another burst.
- Candle data for the first N coins in your loop and errors for the rest, so your universe silently shrinks to whatever sorted first.
- Order placement failing, because the
/exchangeactions share the same IP budget as your reads.
The structural problem is that every bot re-downloads bars that have not changed. A 1h candle closes once an hour. Fetching the trailing 200 bars every 30 seconds re-sends 199 bars you already hold, and pays 20 weight each time for the privilege.
How Do I Get Candles for 25 Hyperliquid Coins in One Request?
GET /api/v1/hyperliquid/candles/batch returns the same Hyperliquid bars (they come from candleSnapshot) for up to 25 coins in one call, keyed by coin. The bars are cached on our side, so repeat pulls do not touch Hyperliquid at all and your IP's weight budget is untouched.
curl -H "X-API-Key: cdk_live_your_key" \
"https://cryptodataapi.com/api/v1/hyperliquid/candles/batch?coins=BTC,ETH,SOL,HYPE,kPEPE&interval=1h&limit=200&closed_only=true"
A representative response (example values, trimmed):
{
"interval": "1h",
"limit": 200,
"series": {
"BTC": [{"timestamp": 1761800400000, "open": 70210.0, "high": 70480.0,
"low": 70105.0, "close": 70390.0, "volume": 1834.2}, "..."],
"ETH": ["..."], "SOL": ["..."], "HYPE": ["..."]
},
"count": 4,
"pending": ["kPEPE"],
"complete": false,
"retry_after_s": 3,
"closed_only": true,
"atr_period": null,
"forming_bar_timestamp": null
}
The contract is explicit, and worth reading once:
completeistruewhen every requested coin is inseries. A coin is never in bothseriesandpending.pendingnames coins whose series had not arrived within thewaitwindow (0 to 25 seconds, default 8). The fetch keeps running in the background and is cached, so re-request only those coins.retry_after_sis the wait hint, 3 to 30 seconds scaled to how many coins are still fetching. It isnullwhen nothing is pending.closed_only=truereturns the lastlimitcompleted bars, soforming_bar_timestampisnull. That is the read for a strategy that acts on a closed bar.- A coin Hyperliquid does not list is a
400 unknown_coin. More than 25 coins is a400 too_many_coins. Nothing is silently dropped, and an unrecognised query parameter is a 400 too.
Coin names resolve case-insensitively against Hyperliquid's own names, so kPEPE works and so does kpepe. symbols is accepted as an alias for coins, and an atr=14 parameter adds a Wilder ATR to every bar.
Open Interest and Prices for Every Asset, One Call Each
The other two reads a scanner needs are the whole-universe ones, and both are a single request here.
GET /api/v1/hyperliquid/open-interest returns every listed Hyperliquid perp in one assets array. Each row carries coin, funding_rate, open_interest (base-coin units), open_interest_usd, mark_price, oracle_price, premium, day_ntl_vlm and prev_day_px. The envelope adds total_oi_usd, as_of (ms, when the snapshot was fetched from Hyperliquid), generated_at (ms, when the response was built) and coverage, which is {universe_total, delisted_excluded, rows}: Hyperliquid keeps delisted markets in its universe and this route drops them.
curl -H "X-API-Key: cdk_live_your_key" \
"https://cryptodataapi.com/api/v1/hyperliquid/open-interest"
GET /api/v1/hyperliquid/prices is the allMids equivalent: a prices map of coin to mid, a count, and the same as_of / generated_at pair. The gap between the two timestamps is the payload's staleness, and both are null only on a cache row written before the field existed.
Because the rate for every perp is already on the open-interest route (assets[].funding_rate), a funding scan across the universe is one request, not one per coin.
Python: Retry Only What Is Pending
The loop below handles a cold cache correctly. It asks once, then re-requests only the coins named in pending after retry_after_s, and never re-downloads a series it already holds.
import time, httpx
BASE = "https://cryptodataapi.com/api/v1"
H = {"X-API-Key": "cdk_live_your_key", "Accept-Encoding": "gzip"}
def batch_candles(coins, interval="1h", limit=200):
series, todo = {}, list(coins)
while todo:
r = httpx.get(f"{BASE}/hyperliquid/candles/batch", headers=H, timeout=40,
params={"coins": ",".join(todo), "interval": interval,
"limit": limit, "closed_only": "true", "wait": 10})
r.raise_for_status()
body = r.json()
series.update(body["series"])
todo = body["pending"]
if todo:
time.sleep(body["retry_after_s"] or 3)
return series
bars = batch_candles(["BTC", "ETH", "SOL", "HYPE", "kPEPE"])
print({c: len(v) for c, v in bars.items()})
After the first fill, pull a small limit on each bar close and append. An 8-coin limit=80 batch is about 66 KB and returns in milliseconds from cache; a limit=800 batch of the same coins is roughly 670 KB uncompressed and 146 KB with gzip. Pull deep history once, then top up.
Direct Polling vs the Cached Batch Route
Direct candleSnapshot loop | /hyperliquid/candles/batch | |
|---|---|---|
| Requests for 25 coins | 25 | 1 |
| Hyperliquid weight spent per sweep | 500 + surcharges, from your IP | 0 on a warm cache; a cold coin costs about 20 weight on our side, once |
| Partial failure | Some coins 429, some succeed, you sort it out | complete / pending / retry_after_s tell you exactly what to retry |
| Forming bar | Always included, you strip it | closed_only=true removes it |
| 4h bars for liquid perps | Fetched on demand | Refilled for the ~60 most liquid perps just after each 4h close, so the first read after a close is instant |
| Your own quota | Hyperliquid's 1,200 weight/min per IP | Our per-key limit: 10/min on Free, 30/min on Pro, 120/min on Pro Plus |
The honest caveat is that a cold start still costs Hyperliquid weight somewhere. A first pull of many uncached coins takes about a minute of budget on our side, which is why the route answers with pending rather than holding your connection open. After that, repeat pulls are served from cache.
How to Use This in a Bot (and Where It Does Not Help)
Use the batch route for the scan: one call per bar close for your whole universe, closed_only=true, a trailing limit sized to your longest indicator. Use /hyperliquid/open-interest for funding and OI across every coin in the same tick, and /hyperliquid/prices for mids between bars. All three are available on a free key, which allows 10 requests a minute and 1,000 a day once the email is confirmed.
What it does not do:
- Date ranges. The batch route is trailing-
limitonly. For an explicitstart/endwindow use the single-coin/hyperliquid/candles, and know that range requests bypass the cache and spend live Hyperliquid weight, so poll them sparingly. - Deep history. Hyperliquid keeps only its most recent ~5,000 bars per interval (about 3.5 days of 1m, 17 days of 5m, 7 months of 1h). Multi-month 1m history is
/backtesting/klines?exchange=hyperliquid, not a biggerlimit. - Sub-second data. This is a REST cache with a 60-second response cache on trailing pulls. A market-making loop needs Hyperliquid's WebSocket, which has its own limits listed above.
- Your order flow. Placing orders still spends your own IP and address budgets on
/exchange. Moving the reads off that IP is exactly what frees headroom for them.
Fees for sizing are on /hyperliquid/meta (fees.perps.taker is 0.00045), and funding plus OI aligned to the same bar grid is /backtesting/hl-funding-bars, joined on timestamp. The full parameter list is in the docs.
Frequently asked questions
What is the Hyperliquid API rate limit?
REST requests from one IP share an aggregated weight limit of 1,200 per minute. Most /info requests cost 20 weight, l2Book and allMids cost 2, userRole costs 60, and candleSnapshot adds weight per 60 candles returned.
How many Hyperliquid coins can I fetch candles for in one request?
Up to 25 with GET /api/v1/hyperliquid/candles/batch. The response has a series map keyed by coin, a pending list for coins still fetching on a cold cache, a complete flag and a retry_after_s hint.
What does retry_after_s mean on the candles batch endpoint?
It is how long to wait, 3 to 30 seconds, before re-requesting only the coins named in pending. It is null when nothing is pending. The background fetch keeps running, so the retry joins it rather than starting over.
Does closed_only=true change which bars I get?
Yes. It returns the last limit completed bars with no forming bar, and forming_bar_timestamp is null. Use it for strategies that act on a closed candle.
How do I read open interest for every Hyperliquid perp at once?
GET /api/v1/hyperliquid/open-interest returns every listed perp in one assets array with funding_rate, open_interest, open_interest_usd, mark_price and oracle_price, plus a coverage block that says how many delisted markets were excluded.



