| name | api |
| description | Complete Hyperliquid API reference — /info reads, /exchange signed actions, WebSocket subscriptions. The definitive guide for AI agents building on Hyperliquid. |
Hyperliquid API Reference
Architecture
POST /info — Reads (no auth, no signing required)
POST /exchange — Writes (signed L1 actions required)
WS /ws — Subscriptions (real-time data)
Mainnet:
- REST:
https://api.hyperliquid.xyz
- WebSocket:
wss://api.hyperliquid.xyz/ws
Testnet:
- REST:
https://api.hyperliquid-testnet.xyz
- WebSocket:
wss://api.hyperliquid-testnet.xyz/ws
Critical: Asset IDs
Asset IDs are required for all order actions. Get them wrong and orders fail silently or go to the wrong market.
Perps: asset = index in meta.universe array
Spot: asset = 10000 + index in spotMeta.universe array
HIP-3: asset = 100000 + (perp_dex_index * 10000) + index_in_perp_meta
Always fetch meta and spotMeta at startup and cache them. Never hardcode asset IDs.
from hyperliquid.info import Info
from hyperliquid.utils import constants
info = Info(constants.MAINNET_API_URL)
meta = info.meta()
ASSET_IDS = {m['name']: i for i, m in enumerate(meta['universe'])}
print(ASSET_IDS['BTC'])
print(ASSET_IDS['ETH'])
POST /info — Read Endpoints
Market Data
info.meta()
info.spot_meta()
info.all_mids()
info.l2_snapshot("BTC")
info.candles_snapshot("BTC", "1h", start_time_ms, end_time_ms)
info.recent_trades("BTC")
info.funding_history("BTC", start_time_ms)
info.meta_and_asset_ctxs()
Account Data
info.user_state(address)
info.spot_user_state(address)
info.open_orders(address)
info.user_fills(address)
info.user_fills_by_time(address, start_time_ms)
info.order_status(address, order_id)
info.referral(address)
info.user_funding(address, start_time_ms)
info.portfolio(address)
POST /exchange — Signed Actions
All exchange actions require:
- Build the action payload
- Sign with EIP-712 or agent wallet
- POST to
/exchange with { action, nonce, signature }
Use the official SDK. Signing correctly from scratch is error-prone.
Nonces
Hyperliquid uses a rolling nonce window, not linear nonces like Ethereum.
import time
nonce = int(time.time() * 1000)
Requirements:
- Nonce must be unique per signer
- Must be within the valid time window (~60 seconds of current time)
- Using the same nonce twice = rejected
- Never share a signer across concurrent processes without atomic nonce allocation
Place Order
from hyperliquid.exchange import Exchange
from hyperliquid.utils import constants
import eth_account
private_key = "0x..."
account = eth_account.Account.from_key(private_key)
exchange = Exchange(account, constants.MAINNET_API_URL)
result = exchange.market_open(
coin="BTC",
is_buy=True,
sz=0.001,
slippage=0.01,
)
print(result)
result = exchange.order(
coin="BTC",
is_buy=True,
sz=0.001,
limit_px=90000.0,
order_type={"limit": {"tif": "Gtc"}},
)
result = exchange.market_close(coin="BTC")
result = exchange.cancel(coin="BTC", oid=order_id)
result = exchange.cancel_by_cloid(coin="BTC", cloid="your-cloid")
Order Types
order_type = {"limit": {"tif": "Gtc"}}
order_type = {"limit": {"tif": "Alo"}}
order_type = {"limit": {"tif": "Ioc"}}
order_type = {
"trigger": {
"isMarket": True,
"triggerPx": "90000.0",
"tpsl": "sl"
}
}
Batch Orders
orders = [
{
"coin": "BTC",
"is_buy": True,
"sz": 0.001,
"limit_px": 90000.0,
"order_type": {"limit": {"tif": "Gtc"}},
"reduce_only": False,
},
{
"coin": "ETH",
"is_buy": False,
"sz": 0.1,
"limit_px": 3600.0,
"order_type": {"limit": {"tif": "Gtc"}},
"reduce_only": False,
}
]
result = exchange.bulk_orders(orders)
Account Management
exchange.update_leverage(leverage=10, coin="BTC", is_cross=True)
exchange.update_isolated_margin(amount=100.0, coin="BTC")
exchange.withdraw_from_bridge(amount=100.0, destination=address)
exchange.usd_transfer(amount=100.0, destination=address)
API Wallet (Agent Wallet)
API wallets sign on behalf of a master account. Use for automation to avoid using your main key.
from hyperliquid.exchange import Exchange
result = master_exchange.approve_agent(
agent_address=api_wallet_address,
agent_name="my-bot"
)
api_account = eth_account.Account.from_key(api_private_key)
api_exchange = Exchange(
api_account,
constants.MAINNET_API_URL,
vault_address=master_address
)
result = api_exchange.market_open("BTC", True, 0.001, 0.01)
WebSocket Subscriptions
import WebSocket from 'ws';
class HyperliquidWS {
constructor(url = 'wss://api.hyperliquid.xyz/ws') {
this.url = url;
this.ws = null;
this.subscriptions = new Map();
this.connect();
}
connect() {
this.ws = new WebSocket(this.url);
this.ws.on('open', () => {
console.log('Connected');
for (const [id, sub] of this.subscriptions) {
this.ws.send(JSON.stringify({ method: 'subscribe', subscription: sub }));
}
. = ( {
..(.({ : }));
}, );
});
..(, {
(.);
( .(), );
});
..(, {
msg = .(raw);
.(msg);
});
}
() {
id = .(subscription);
..(id, { subscription, handler });
..(.({ : , subscription }));
}
() {
(msg. === ) ;
( [, { subscription, handler }] .) {
(msg. === subscription.) {
(msg.);
}
}
}
}
hl = ();
hl.({ : , : }, {
[bids, asks] = data.;
.();
});
hl.({ : }, {
.(, data.?.);
});
hl.({ : , : }, {
( fill data.) {
.();
}
});
hl.({ : , : }, {
.(, data);
});
hl.({ : , : }, {
( trade data) {
.();
}
});
All WebSocket Subscription Types
| Type | Params | Data |
|---|
allMids | none | All mid prices, every second |
l2Book | coin | Order book updates |
trades | coin | Recent trades stream |
candle | coin, interval | OHLCV candle updates |
userEvents | user | Order updates, fills, liquidations |
userFills | user | Fill notifications |
userFundings | user | Funding payment notifications |
userNonFundingLedgerUpdates | user | Deposits, withdrawals, transfers |
notification | user | Liquidation warnings |
webData2 | user | Full user state snapshot |
Rate Limits
IP-level:
- REST: weighted request limit per minute
- Batching orders helps IP budget but still counts per-action for address limits
- WebSocket: connection, subscription, and message limits apply
Address-level:
- Action throughput tied to account history and volume tier
- A batch of 10 orders = 1 IP request, but 10 address-level actions
Design around both limits, not just one.
Error Handling
result = exchange.market_open("BTC", True, 0.001, 0.01)
if result['status'] == 'ok':
statuses = result['response']['data']['statuses']
for status in statuses:
if 'filled' in status:
print(f"Filled at {status['filled']['avgPx']}")
elif 'resting' in status:
print(f"Resting order ID: {status['resting']['oid']}")
elif 'error' in status:
print(f"Order error: {status['error']}")
elif result['status'] == 'err':
print(f"Exchange error: {result['response']}")
Common Errors
| Error | Cause | Fix |
|---|
Nonce too low | Reused nonce, clock skew | Use int(time.time() * 1000), ensure monotonic |
User or API wallet does not exist | Wrong signing key or vault address | Check account registration, API wallet approval |
Insufficient margin | Not enough collateral | Deposit more or reduce size |
Invalid sz | Size violates szDecimals | Round to correct decimal places from meta |
Invalid px | Price violates tick constraints | Use SDK's price normalization |
Post-only rejected | ALO order would have taken | Market has moved; retry or switch to GTC |
Pre-Integration Checklist
[ ] Fetched meta and built asset ID lookup
[ ] Using millisecond timestamps for nonces
[ ] Nonce allocator is atomic (no concurrent reuse)
[ ] API wallet registered (not using main key for automation)
[ ] Tested full order lifecycle on testnet
[ ] Error handling covers: fill, resting, error statuses
[ ] WebSocket reconnect logic implemented
[ ] Rate limit awareness (batch where possible)
[ ] Kill switch for repeated rejections