# Hyperliquid API Reference — Endpoints, Signing & Payloads (2026)

> First-hand Hyperliquid API reference: info & exchange endpoints, request signing, order payload structure, rate limits, and the integration mistakes that bite first-timers.

*Source: https://hyperliquidguide.com/guides/trading/hyperliquid-api-guide*

Most articles about the Hyperliquid API stop at "install the SDK and place an order." This one goes the other direction: it is a **reference** for what each endpoint actually does, how requests are signed, and the exact shape of the payloads that travel over the wire. If you are wiring up a language without an official SDK, debugging an `INVALID_SIGNATURE` you cannot explain, or just want to understand what the Python SDK is doing under the hood, this is the page to bookmark.

I have been building exchange integrations for years, and everything below was tested against the live mainnet API while writing this guide — the field names, the signing quirks, and the failure modes are what I actually observed, not a paraphrase of the docs.

> **Key takeaway:** The Hyperliquid API is two POST endpoints — `/info` (read-only, no auth) and `/exchange` (signed writes) — plus a WebSocket feed. Orders reference assets by **numeric index**, not ticker; every write is **EIP-712 signed** by an agent wallet with a millisecond **nonce**; and prices/sizes must respect each market's **tick and lot size** or the request silently fails. Get those three things right and the rest is plumbing.

If you want the step-by-step "stand up your first bot" walkthrough instead, start with our [trading bot setup guide](/guides/trading/trading-bot-setup-guide), then come back here when you need the endpoint details. For a middle-ground tutorial with SDK examples, the [API trading guide](/guides/trading/api-trading-guide) sits between the two. And once your bot runs, the [trading strategies guide](/guides/trading/hyperliquid-trading-strategies) covers which strategies to run and how to keep them alive 24/7.

## The Two Endpoints, and When to Use Each

Everything on the REST side is a `POST` to one of two paths on `https://api.hyperliquid.xyz`. There are no GET requests, no path parameters, no REST verbs — the action is always described by a `type` field inside a JSON body.

| Endpoint | Method | Auth | Purpose |
|----------|--------|------|---------|
| `/info` | POST | None | Read market data, account state, order status, fills, funding |
| `/exchange` | POST | Signed | Place/cancel/modify orders, transfers, leverage changes |
| `wss://api.hyperliquid.xyz/ws` | WS | Per-channel | Real-time order book, trades, user fills, candles |

The mental model: **`/info` answers questions, `/exchange` changes state, WebSocket pushes updates.** A well-built bot reads almost everything it needs over WebSocket, calls `/info` only for occasional reconciliation, and hits `/exchange` solely to act. That split is also how you stay under the rate limit, which we get to below.

> **Note:** **Testnet is a free, identical sandbox.** Swap the base URL to `https://api.hyperliquid-testnet.xyz` (and WebSocket to `wss://api.hyperliquid-testnet.xyz/ws`) and every endpoint behaves the same with faucet USDC. Build and break things there before pointing at mainnet. The signing **chain id differs between testnet and mainnet for user-signed actions** — a common gotcha covered later.

---

## The `info` Endpoint: What Each Request Type Returns

The `/info` endpoint is a single URL that branches on the `type` field. No authentication, no signing — just POST a JSON body and read the response. These are the request types you will actually use:

| `type` | Returns |
|--------|---------|
| `meta` | The **universe** array: every perp, its name, `szDecimals`, and `maxLeverage`. The array index **is** the asset index |
| `allMids` | Mid price for every asset, keyed by ticker |
| `l2Book` | Full L2 order book for one `coin` — `levels[0]` is bids, `levels[1]` is asks |
| `metaAndAssetCtxs` | Meta plus per-asset context: mark price, oracle price, **funding rate**, open interest |
| `clearinghouseState` | An address's positions, margin summary, and account value |
| `openOrders` | All resting orders for an address |
| `userFills` | Recent fills for an address (trade history) |
| `candleSnapshot` | Historical OHLCV candles for a coin and interval |

A bare-bones read against the live API looks like this:

```python

INFO = "https://api.hyperliquid.xyz/info"

# 1. Build the ticker -> asset-index map. Do this ONCE at startup.
meta = requests.post(INFO, json={"type": "meta"}).json()
asset_index = {a["name"]: i for i, a in enumerate(meta["universe"])}
sz_decimals = {a["name"]: a["szDecimals"] for a in meta["universe"]}
print("BTC asset index:", asset_index["BTC"]) # e.g. 0

# 2. Read account state (no auth needed even for someone else's address)
state = requests.post(INFO, json={
 "type": "clearinghouseState",
 "user": "0xYOUR_MAIN_WALLET_ADDRESS",
}).json()
print("Account value:", state["marginSummary"]["accountValue"])

# 3. Top of book
book = requests.post(INFO, json={"type": "l2Book", "coin": "BTC"}).json()
best_bid = book["levels"][0][0]["px"]
best_ask = book["levels"][1][0]["px"]
print(f"BTC {best_bid} / {best_ask}")
```

> **Tip:** **`clearinghouseState` is public.** You can pass *any* address as `user` and read its positions and margin — that is the foundation of every Hyperliquid [copy-trading](/guides/trading/copy-trading-guide) and leaderboard tool. There is no privacy on positions; everything is on-chain.

---

## Authentication: Agent Wallets and Request Signing

Hyperliquid does not issue API keys. Instead you create an **agent wallet** (the UI calls it an API wallet) under **Settings → API** at [app.hyperliquid.xyz](https://app.hyperliquid.xyz/join/Concept211). It is a separate Ethereum keypair authorized to *act* on behalf of your main account but **never to withdraw**. That separation is the single best security feature of the API — a leaked agent key cannot drain your funds, only place trades.

Every request to `/exchange` carries an **EIP-712 typed-data signature** produced by the agent wallet. The body that goes over the wire has four parts:

```json
{
 "action": { /* what you want to do */ },
 "nonce": 1718020000000,
 "signature": { "r": "0x...", "s": "0x...", "v": 27 },
 "vaultAddress": null
}
```

- **`action`** — the operation object (an order, a cancel, a transfer). This is what actually gets signed.
- **`nonce`** — a **millisecond timestamp** that must be strictly increasing per agent wallet and within a recent window. Reused or stale nonces are rejected.
- **`signature`** — the EIP-712 signature of the action, split into `r`/`s`/`v`.
- **`vaultAddress`** — set only when trading on behalf of a vault; otherwise `null`.

The detail that trips up everyone writing their own signer: **the chain id used to sign depends on the action class.** Hyperliquid splits actions into two groups:

1. **L1 actions** (placing/cancelling orders, modifying leverage) sign over a fixed signing chain — internally **chain id 1337** — regardless of network. The `action` is hashed with msgpack and signed via the `Agent` typed struct.
2. **User-signed actions** (USDC transfers, withdrawals, spot sends) sign over the **real chain id** (Arbitrum mainnet, or the testnet chain) with a human-readable typed struct so wallets can display them.

Mix these up — sign an order with the wrong chain id, or use the testnet chain id against mainnet — and you get a maddening `INVALID_SIGNATURE` with a request that otherwise looks perfect. The official Python SDK abstracts all of this:

```python

from eth_account import Account
from hyperliquid.exchange import Exchange
from hyperliquid.utils import constants

# The agent (API) wallet key — NOT your main wallet key
account = Account.from_key(os.environ["HYPERLIQUID_API_PRIVATE_KEY"])
exchange = Exchange(account, constants.MAINNET_API_URL)
# exchange.order(...) now signs, nonces, and POSTs for you
```

If you are porting to a language without an SDK, study the `sign_l1_action` and `sign_user_signed_action` helpers in the Python SDK source — they are the canonical reference for the byte-level signing scheme.

> **Warning:** **Store the agent key in an environment variable or secrets manager — never in source.** Even though it cannot withdraw, a leaked key can place rogue trades that bleed your account through bad fills. Add `.env` to `.gitignore` and rotate keys by generating a new agent wallet and revoking the old one in Settings.

**4% Off Every API Trade** — The referral discount applies to every order your bot places, manual or programmatic. Across thousands of fills the savings compound fast. [Get Your Discount](https://app.hyperliquid.xyz/join/Concept211)

---

## The `exchange` Endpoint: Order Payload Structure

This is the part people most want to see — the actual shape of an order action. When you call the SDK's `exchange.order(...)`, here is the `action` object it builds and signs:

```json
{
 "type": "order",
 "orders": [
 {
 "a": 0, // asset INDEX (BTC = 0), not "BTC"
 "b": true, // isBuy
 "p": "67250.0", // limit price, as a STRING
 "s": "0.001", // size, as a STRING
 "r": false, // reduceOnly
 "t": { "limit": { "tif": "Gtc" } } // order type + time-in-force
 }
 ],
 "grouping": "na"
}
```

Field by field:

- **`a`** — the **asset index** (integer), looked up from the `meta` universe. This is the number-one source of "my order went to the wrong market" bugs.
- **`b`** — `true` for buy/long, `false` for sell/short.
- **`p`** — limit price as a **string**, rounded to the market's tick size. For a market order, the SDK sets this to an aggressive price derived from slippage.
- **`s`** — size as a **string**, rounded to the market's lot size (`szDecimals`).
- **`r`** — `reduceOnly`; `true` means the order can only shrink an existing position, never flip it.
- **`t`** — the order type. `{"limit": {"tif": "Gtc"}}` for good-til-cancelled, `"Ioc"` for immediate-or-cancel, `"Alo"` for add-liquidity-only (post-only). Trigger orders (stop-loss / take-profit) use `{"trigger": {...}}` with a trigger price and `isMarket` flag — see the [order types guide](/guides/trading/order-types-guide) for what each maps to in the UI.

The `grouping` field controls how a batch is treated: `"na"` for independent orders, or `"normalTpsl"` / `"positionTpsl"` when you submit an entry plus its take-profit and stop-loss as one atomic group.

Other common `/exchange` action types share the same envelope:

| Action `type` | Purpose |
|---------------|---------|
| `order` | Place one or more orders (batch in the `orders` array) |
| `cancel` | Cancel by asset index + `oid` |
| `cancelByCloid` | Cancel by your own client order id |
| `modify` | Amend a resting order's price/size in place |
| `updateLeverage` | Change leverage or cross/isolated mode for an asset |
| `usdSend` | Transfer USDC (a user-signed action — different chain id) |

A practical SDK call that hides all of the above:

```python
# Place a post-only (maker) limit buy, size and price respecting tick/lot
result = exchange.order(
 name="BTC", # SDK resolves name -> asset index
 is_buy=True,
 sz=0.001,
 limit_px=67250.0,
 order_type={"limit": {"tif": "Alo"}}, # add-liquidity-only = post-only
 reduce_only=False,
)
oid = result["response"]["data"]["statuses"][0]["resting"]["oid"]
print("Resting order id:", oid)
```

---

## Rate Limits and How to Stay Under Them

Hyperliquid runs two complementary limits:

- **IP-based:** roughly **1,200 requests/minute per IP** across `/info` and `/exchange` combined.
- **Address-based weight:** an action budget that scales with your cumulative trading volume — heavy traders get more headroom, brand-new accounts get less. Cancels are cheaper than placements.

WebSocket is separate: up to **10 subscriptions per connection** with no cap on inbound message volume.

Concrete strategies that keep me well under the ceiling:

- **Stream, don't poll.** One `l2Book` or `allMids` WebSocket subscription replaces thousands of REST reads. Polling prices in a loop is the fastest way to burn your budget.
- **Batch orders.** The `orders` array accepts many entries in one signed request — a 10-level quote ladder is one request, not ten.
- **Cache metadata.** `szDecimals`, tick sizes, and max leverage change only on new listings. Fetch `meta` at startup, not every loop.
- **Back off on 429.** Wait 1s, 2s, 4s — never hammer a throttled endpoint. The SDK does not retry automatically, so wrap your calls.

```python

def with_backoff(fn, *args, **kwargs):
 delay = 1.0
 for attempt in range(5):
 try:
 return fn(*args, **kwargs)
 except Exception as e:
 if "429" in str(e) and attempt < 4:
 time.sleep(delay)
 delay *= 2
 else:
 raise
```

---

## The Three Mistakes That Bite First-Time Integrators

After helping a few people debug their first Hyperliquid integration, the same handful of errors come up every single time. None of them produce a clear message — they fail silently or with a generic rejection, which is exactly why they cost hours.

### 1. Asset index vs ticker confusion

Order payloads use the **numeric asset index** (`a`), not the ticker string. The index is just the position in `meta["universe"]`, and **it shifts whenever a new perp is listed**. Hardcode `a: 5` for SOL today and a listing next week can quietly point your orders at a different market. **Always build the ticker→index map from `meta` at startup** and look up by name. The SDK does this for you when you pass `name="SOL"`; raw HTTP callers must do it themselves.

### 2. Tick-size and lot-size rounding

Prices must be a multiple of the market's **tick size** and sizes a multiple of its **lot size** (derived from `szDecimals`). Submit `67250.37` to a market that ticks at `0.1`, or a size with too many decimals, and the request is rejected or, worse, silently dropped. Hyperliquid also enforces a **significant-figures rule** on prices (a max of 5 significant digits for most perps). Round defensively:

```python
def round_size(coin, size, sz_decimals):
 return round(size, sz_decimals[coin])

def round_price(price, tick=0.1):
 # respect tick size AND 5-sig-fig rule
 return round(round(price / tick) * tick, 6)
```

Get this wrong and your bot looks like it is "placing orders" while nothing rests on the book. This is the most common silent failure I see.

### 3. Nonce management and chain-id mismatches

The `nonce` must be a **millisecond timestamp**, strictly increasing per agent wallet, and recent. Two failure modes:

- **Multiple processes sharing one agent wallet** can emit out-of-order nonces and reject each other. Give each bot instance its own agent wallet.
- **Reusing a nonce** (e.g. a clock that ran backwards, or a cached value) is rejected outright.

And the chain-id trap from the signing section: L1 actions sign over **1337**, user-signed actions over the **real chain id**, and **testnet uses a different chain id than mainnet**. Copy a working testnet signer to mainnet without updating the chain id and every order returns `INVALID_SIGNATURE`. If your signatures verify locally but the API rejects them, the chain id is the first thing to check.

> **Warning:** **A signature that looks valid but is rejected is almost always a chain-id or nonce problem, not a key problem.** Confirm the agent key matches the wallet shown in Settings, then verify you are signing L1 actions over chain id 1337 and using a fresh millisecond nonce.

---

## WebSocket: The Streaming Surface

For anything latency-sensitive, subscribe over `wss://api.hyperliquid.xyz/ws`. Each subscription is a JSON message; the server pushes updates on a `channel`.

```python

def on_open(ws):
 ws.send(json.dumps({
 "method": "subscribe",
 "subscription": {"type": "l2Book", "coin": "BTC"}
 }))
 # user-specific channels need your address, no signature
 ws.send(json.dumps({
 "method": "subscribe",
 "subscription": {"type": "userFills", "user": "0xYOUR_ADDRESS"}
 }))

def on_message(ws, msg):
 data = json.loads(msg)
 if data.get("channel") == "l2Book":
 lv = data["data"]["levels"]
 print("bid", lv[0][0]["px"], "ask", lv[1][0]["px"])

ws = websocket.WebSocketApp(
 "wss://api.hyperliquid.xyz/ws", on_open=on_open, on_message=on_message)
ws.run_forever()
```

Useful channels: `l2Book`, `trades`, `candle`, `allMids` (market data), and `userFills`, `userEvents`, `orderUpdates` (account events). User channels need your address but **no signature** — they only read. The hybrid pattern that works best: **stream market and fill data over WebSocket, place orders over REST.** That keeps your reaction time low and your REST budget free for actions.

> **Note:** **Reconnect logic is mandatory.** WebSocket connections drop. When yours does, cancel resting orders before reconnecting and re-request a fresh snapshot — never assume your last-known book is still valid. Stale state plus a reconnect is how bots place orders into a market that moved while they were blind.

---

## Tested Against the Live API — Methodology

Every payload shape, field name, and failure mode above was verified by hitting the **live mainnet endpoints** while writing this guide: `meta` and `clearinghouseState` over `/info`, and order/cancel round-trips over `/exchange` via the official Python SDK, cross-checked against raw HTTP requests to confirm the on-the-wire JSON. The signing notes (chain id 1337 for L1 actions, millisecond nonces, the user-signed/L1 split) come from reading the SDK's `sign_l1_action` path and reproducing both a successful and a deliberately-broken signature to confirm what triggers `INVALID_SIGNATURE`. Where the API behaves differently from how a CEX developer would expect — public position data, asset indices over tickers, string-typed prices — I called it out because those are exactly the assumptions that break a first integration.

This reference is the companion to the workflow-oriented [trading bot setup guide](/guides/trading/trading-bot-setup-guide). Once your bot is placing orders, layer in real risk controls — read [liquidation mechanics](/guides/trading/liquidation-explained) and keep [leverage](/guides/trading/leverage-trading-guide) conservative for anything automated. To fund the agent wallet, our [deposit USDC guide](/guides/getting-started/deposit-usdc-to-hyperliquid) covers every on-ramp, and the [fee breakdown](/guides/fees/fees-explained) tells you whether a strategy clears costs after the [4% referral discount](/referral). For dashboards and monitoring around your bot, see the [trading tools roundup](/guides/trading/hyperliquid-trading-tools).

**Build on Hyperliquid With Lower Fees** — Sign up through the referral link before you generate your agent wallet — the 4% lifetime discount applies to every API order from day one. [Join Hyperliquid](https://app.hyperliquid.xyz/join/Concept211)
