Conventions
Shared conventions for all Public Data API endpoints — response envelope, symbol formats, pagination, timestamps, rate limits, credits, data freshness, recipes, and errors.
These conventions apply to every endpoint in the Public Data API (HyperLiquid endpoints, Markets, Stocks, News, and Account).
Response envelope
Every response is wrapped in the same envelope. code is "0" on success; data holds the payload.
{ "code": "0", "data": { /* endpoint payload */ }, "msg": "success" }All numeric values that can be large or fractional (balances, prices, PnL) are returned as strings to preserve precision — parse them with a big-decimal library, not floats.
Symbol & coin formats
HyperLiquid coin identifiers
The coin parameter format on HyperLiquid endpoints depends on the market:
| Market | Format | Example |
|---|---|---|
| Spot | token symbol | PURR |
| Perp | symbol | BTC |
| HIP-3 (stock perps) | dex:SYMBOL | xyz:TSLA |
To discover every currently-listed coin, call /hl/prices/mids — its response keys are the valid coin values (spot, perp, and dex:-prefixed HIP-3 symbols).
Market Data symbols
Every Market Data endpoint is symbol-first — you never pass an internal id. Use the asset's symbol:
| Asset class | Format | Example |
|---|---|---|
| Crypto | ticker | BTC |
| Stock | ticker, bare or market-qualified | NVDA / NVDA-US |
| HIP-3 (tokenized stock perp) | dex:SYMBOL | xyz:TSLA |
Every symbol-keyed endpoint accepts both the bare ticker and the market-qualified form interchangeably — NVDA and NVDA-US resolve the same on /assets/{symbol}, klines, tickers, and stock detail endpoints (fundamentals, company, news, SEC filings). A bare crypto ticker like BTC resolves to the canonical asset (Bitcoin itself, not a same-symbol ETF). Symbol matching is case-insensitive (btc = BTC).
If you are unsure which symbol an asset uses, look it up with /search first. A symbol that matches no asset in either form returns 404.
Asset types & locales
type∈CRYPTO·STOCK·ETF·FOREX·COMMODITY(some list endpoints also acceptALL).- News
locale∈en·zh-CN·zh-TW·fr·ru·es·ar·ko. When a locale's AI analysis isn't ready yet, the item falls back to its base (English) content.
Pagination
List endpoints accept page (1-based) and limit, and return a flat pagination envelope alongside the items:
{ "items": [ /* … */ ], "page": 1, "limit": 100, "total": 17161, "hasMore": true }Defaults and caps vary by endpoint (see each below). total — the total number of matching rows when the source provides a count; null when it does not (always null for news and news search). hasMore is always present. Page until hasMore is false.
Cursor pagination
Three endpoints support an optional opaque cursor token for unbounded deep paging: GET /hl/spot/holders, GET /hl/perp/holders, and GET /hl/leaderboard. All other list endpoints use offset paging only.
When cursor paging is available, responses include "nextCursor": "<token>" | null alongside the standard pagination fields. To page forward, pass the previous response's nextCursor as ?cursor=<token>; stop when nextCursor is null (equivalently hasMore: false).
cursorandpageare mutually exclusive — whencursoris present,pageis ignored. Keeplimitconstant across a cursor sequence.- The token is opaque (base64url), bound to the exact sort (
coin/sortBy/sort+order). Do not hand-craft it or reuse it across different sort configurations — a mismatched cursor returns400 VALIDATION_ERROR. - A cursor minted just before a snapshot refresh resumes at the same rank in the new snapshot; rows may shift slightly across a refresh (the data is a periodic snapshot).
Offset cap: offset paging is capped at the first 10,000 rows — requests where (page − 1) × limit ≥ 10,000 return 400 VALIDATION_ERROR. To read rows beyond 10,000, use cursor paging (cursor is not capped).
Timestamps & freshness
HyperLiquid endpoints
Times such as updatedAt and snapshot_time are ISO-8601 UTC strings (e.g. "2026-06-25T13:06:22.103Z"). Data is served from periodic snapshots (typically refreshed every few seconds to a minute), so values are near-real-time, not tick-by-tick. snapshot_height is a numeric block height, not a time.
BREAKING CHANGE — the following response fields changed type from number (epoch ms) to string (ISO-8601 UTC):
updatedAt— in/hl/spot/holders,/hl/perp/holders,/hl/leaderboard,/hl/hip3/smart-trader,/hl/hip4/smart-tradersnapshot_time— in/hl/trending
snapshot_height remains a number (block height). Update code that treated these as numbers — new Date(updatedAt) and Date.parse(updatedAt) accept ISO strings directly.
Market Data endpoints
Timestamps in Market Data responses are ISO 8601 strings (e.g. 2026-06-26T08:30:00.000Z) — not the epoch-millisecond integers used by the HyperLiquid endpoints. Parse them as dates.
Rate limits & credits
Each key is rate-limited by requests per minute. On paid tiers, each successful call (HTTP 2xx) also draws from a monthly credit quota.
- Rate limit — a fixed per-minute window per key. The counter resets at the top of each clock minute. Every response carries
X-RateLimit-Limit(your per-minute cap),X-RateLimit-Remaining(calls left in the current minute window), andX-RateLimit-Reset(Unix epoch seconds when the window resets at the top of the next clock minute). Pace your requests againstX-RateLimit-Remaining. Exceeding your cap returns429 Too Many Requestswith aRetry-Afterheader (seconds until the window resets) in addition to theX-RateLimit-*headers. - Credits (paid tiers) — each endpoint has a per-call credit cost (see Credit costs). A call is billed only on success (HTTP
2xx) — failed requests (4xx/5xx) cost nothing, and each successful request is billed exactly once. The Pro and Max quotas are deliberately sized well above what the rate limit can spend in a month, so you'll rarely reach them; if a monthly quota is exhausted, calls return403with codeINSUFFICIENT_CREDITSuntil it resets at the start of the next period. - Free tier — rate-limited only, with no credit cost.
The tiers table (Free / Pro / Max rate limits and monthly credit quotas) is on the Authentication page. Check your remaining balance on the Usage tab of the developer portal, or per-endpoint via GET /usage.
Credit costs
| Credits | Endpoints |
|---|---|
| 0 | /usage |
| 1 | /hl/spot/holders, /hl/perp/holders, /hl/prices/mids, /hl/trending, /hl/leaderboard |
| 1 | /news (feed), /news/{id}, /assets, /assets/{symbol}, /global-stats, /trending-gainers, /assets/{symbol}/tickers, /stock/overview, /stock/{symbol}/fundamentals, /stock/{symbol}/company, /stock/{symbol}/news, /rwa |
| 2 | /hl/perp/liquidation-map, /hl/hip3/smart-trader, /hl/hip4/smart-trader |
| 2 | /news/search, /search, /assets/{symbol}/klines, /stock/{symbol}/sec-filings |
Costs may change over time; treat these as current defaults, not a contract.
Data freshness & degraded responses
Each Market Data endpoint is served through a short-lived cache — typically 30–60 seconds, and up to about an hour for equity fundamentals (/stock-info) and SEC filings (/sec-filings) — so values are near-real-time. During a brief upstream outage the API keeps serving the most recently cached value (up to roughly an hour old) instead of failing; responses do not carry a separate "stale" flag, so treat every value as point-in-time. If no cached value is available while the upstream is unreachable, the call returns 502 (MARKET_UPSTREAM_ERROR); if the market-data service is disabled or not configured, it returns 503 (MARKET_DATA_DISABLED). Responses expose only public, cleaned fields — internal upstream metadata is stripped and never returned.
HyperLiquid endpoints are served from periodic snapshots (typically every few seconds to a minute).
Recipes
Paginate through every page
Request page=1, accumulate data.items from the response, then check data.hasMore. If data.hasMore is true, request the next page (page=2, 3, and so on), continuing until data.hasMore is false. Keep limit at the endpoint's maximum to minimize the number of requests.
Paginate past 10,000 rows with cursor
Offset paging is capped at 10,000 rows. For the three large-list endpoints (/hl/spot/holders, /hl/perp/holders, /hl/leaderboard), use cursor paging to read the full dataset:
- Fetch page 1 normally:
GET /hl/spot/holders?coin=PURR&limit=100. - If the response includes a non-null
nextCursor, pass it as?cursor=<token>on the next request (omitpage):GET /hl/spot/holders?coin=PURR&limit=100&cursor=<token>. - Repeat, always forwarding the latest
nextCursor, until it isnullorhasMoreisfalse.
Keep limit and sort parameters (coin/sortBy/sort/order) constant across the sequence — changing them invalidates the cursor and returns 400 VALIDATION_ERROR.
Retry with backoff
Honor the Retry-After header on a 429 response — wait that many seconds, then retry. For 502, 503, and other 5xx responses, retry with exponential backoff (for example, wait about 0.5s, then 1s, then 2s between attempts, capping the total number of attempts). Failed attempts (429/5xx) are not billed — only a successful 2xx response is — so retrying is safe and never double-charges for a request that did not succeed.
Errors
Errors use the same envelope, with code non-"0", data: null, and an HTTP status that reflects the problem:
{ "code": "INVALID_API_KEY", "msg": "Invalid or revoked API key", "data": null }| Status | code | Meaning | Retry? |
|---|---|---|---|
400 | VALIDATION_ERROR | Invalid parameter — unknown coin, bad enum, out-of-range limit. msg names the offending field. | No — fix the request |
401 | INVALID_API_KEY | Missing or invalid API key | No — check the x-api-key header |
403 | INSUFFICIENT_CREDITS | Monthly credit quota exhausted (paid tiers) | No — wait for reset or upgrade |
403 | FORBIDDEN | Access not permitted for this account/tier | No |
404 | NOT_FOUND | Unknown route or resource | No — check the path |
404 | MARKET_ASSET_NOT_FOUND | Unknown symbol (Market Data endpoints) | No — verify via /search |
429 | RATE_LIMITED | Rate limit exceeded | Yes — wait for Retry-After, then retry |
502 | MARKET_UPSTREAM_ERROR | Upstream data provider error (Market Data) | Yes — retry with backoff |
503 | MARKET_DATA_DISABLED | Market data temporarily unavailable / not configured | Yes — retry with backoff |
500 | INTERNAL_ERROR | Unexpected server error | Yes — retry with backoff; contact support if it persists |
Handling rule of thumb: retry 429/5xx with backoff (see Retry with backoff); fix and don't blindly retry 400/401/403/404.