Hyperliquid API endpoints for mainnet and testnet
The HTTP base URL changes with the network, but the paths stay the same. POST /info returns market data and address state. POST /exchange receives signed orders, cancels and transfers. The /ws endpoint streams public and user channels.
Set the network in one configuration value. Do not let a missing setting select mainnet for a signed action.
| Network | HTTP base | Info | Exchange | WebSocket |
|---|---|---|---|---|
| Mainnet | https://api.hyperliquid.xyz | POST /info | POST /exchange | wss://api.hyperliquid.xyz/ws |
| Testnet | https://api.hyperliquid-testnet.xyz | POST /info | POST /exchange | wss://api.hyperliquid-testnet.xyz/ws |
MAINNET_HTTP = "https://api.hyperliquid.xyz" TESTNET_HTTP = "https://api.hyperliquid-testnet.xyz" MAINNET_WS = "wss://api.hyperliquid.xyz/ws" TESTNET_WS = "wss://api.hyperliquid-testnet.xyz/ws" INFO_PATH = "/info" EXCHANGE_PATH = "/exchange"
Send a public Info request with Python
Public Info requests do not need an API key or a wallet signature. The request body selects the response type. The example gets every active mid price from mainnet and converts each price string to Decimal.
Set a timeout, check the HTTP status and handle rate-limit responses in production. Keep the original strings when exact audit records matter.
from decimal import Decimal
import requests
MAINNET_HTTP = "https://api.hyperliquid.xyz"
response = requests.post(
f"{MAINNET_HTTP}/info",
json={"type": "allMids"},
timeout=10,
)
response.raise_for_status()
mids = {coin: Decimal(px) for coin, px in response.json().items()}
print(mids["BTC"])Install and configure the official Python SDK
The hyperliquid-python-sdk package provides Info, Exchange and WebSocket helpers. Pin the package version that you test. Review release notes before an upgrade because signing and asset rules can change.
For trading, authorize an API wallet and keep its private key outside source control. Pass the master account address as account_address. An API wallet address is not a substitute for the master account address.
pip install hyperliquid-python-sdk # Pin the tested version in your requirements file.
Reconnect a Python WebSocket client and recover a snapshot
The server can disconnect without notice. It also closes a connection when the server has sent no message for 60 seconds. The client below sends an application ping after 50 quiet seconds, reconnects with bounded exponential backoff and subscribes again.
Each l2Book message is a full order-book snapshot. Clear the old book after a disconnect, then replace local state with the first new l2Book message before you serve data again. Time-series feeds such as userFills mark the reconnect snapshot with isSnapshot: true. Reconcile that snapshot by fill identity instead of appending every item as a new event.
import json
import random
import time
from websocket import WebSocketException, WebSocketTimeoutException, create_connection
MAINNET_WS = "wss://api.hyperliquid.xyz/ws"
SUBSCRIBE = {
"method": "subscribe",
"subscription": {"type": "l2Book", "coin": "BTC"},
}
def stream_book():
backoff = 1.0
while True:
ws = None
book = None # Do not serve the pre-disconnect snapshot.
try:
ws = create_connection(MAINNET_WS, timeout=50)
ws.send(json.dumps(SUBSCRIBE))
while True:
try:
raw = ws.recv()
except WebSocketTimeoutException:
ws.send(json.dumps({"method": "ping"}))
continue
if not raw:
raise WebSocketException("connection closed")
if raw == "Websocket connection established.":
continue
message = json.loads(raw)
if message.get("channel") != "l2Book":
continue
book = message["data"] # Replace local state with this snapshot.
backoff = 1.0
print(book["coin"], book["time"], len(book["levels"][0]))
except (OSError, WebSocketException) as exc:
print(f"WebSocket disconnected: {exc}")
time.sleep(backoff + random.random())
backoff = min(backoff * 2, 30.0)
finally:
if ws is not None:
ws.close()
stream_book()Place a reduce-only testnet order with a slippage limit
The example only reduces an existing ETH position on testnet. HL_API_PRIVATE_KEY must contain an API wallet key that is authorized on testnet. HL_ACCOUNT_ADDRESS must contain the master account address.
Exchange.market_close reads the position direction and submits an IOC order with reduce_only=True. Reduce-only prevents the order from increasing or flipping the position. When px=mid is present, the SDK multiplies that supplied mid by 1 + slippage for a buy or 1 - slippage for a sell. The SDK then applies the asset price rules. If px is omitted, the SDK fetches a mid before it applies the slippage multiplier. The response check permits a small rounding tolerance and reports an unfilled or rejected IOC.
import os
from decimal import Decimal
from eth_account import Account
from hyperliquid.exchange import Exchange
from hyperliquid.info import Info
from hyperliquid.utils import constants
COIN = "ETH"
MAX_SLIPPAGE = Decimal("0.005") # 0.5% on testnet.
if not Decimal("0") < MAX_SLIPPAGE <= Decimal("0.01"):
raise ValueError("Set MAX_SLIPPAGE between 0% and 1%.")
account_address = os.environ["HL_ACCOUNT_ADDRESS"]
api_wallet = Account.from_key(os.environ["HL_API_PRIVATE_KEY"])
info = Info(constants.TESTNET_API_URL, skip_ws=True)
exchange = Exchange(
api_wallet,
constants.TESTNET_API_URL,
account_address=account_address,
)
state = info.user_state(account_address)
position = next(
(
row["position"]
for row in state["assetPositions"]
if row["position"]["coin"] == COIN
and Decimal(row["position"]["szi"]) != 0
),
None,
)
if position is None:
raise SystemExit("No testnet ETH position to reduce.")
szi = Decimal(position["szi"])
mid = Decimal(info.all_mids()[COIN])
result = exchange.market_close(
COIN,
sz=float(abs(szi)),
px=float(mid),
slippage=float(MAX_SLIPPAGE),
)
if result.get("status") != "ok":
raise RuntimeError(f"Order request failed: {result}")
status = result["response"]["data"]["statuses"][0]
if "filled" not in status:
raise RuntimeError(f"Reduce-only IOC did not fill: {status}")
avg_px = Decimal(status["filled"]["avgPx"])
is_buy = szi < 0 # Buy to close a short; sell to close a long.
adverse = (avg_px - mid) / mid if is_buy else (mid - avg_px) / mid
if adverse > MAX_SLIPPAGE + Decimal("0.00001"):
raise RuntimeError(f"Fill exceeded the slippage check: {adverse:.3%}")
print(result)Apply Hyperliquid units and rounding rules
Many API numbers are decimal strings. Parse them at the application boundary. In metaAndAssetCtxs, openInterest is a base-asset size and dayNtlVlm is notional volume. Estimated USD open interest is openInterest multiplied by markPx.
A price can have no more than five significant figures and no more than 6 - szDecimals decimal places for perpetuals. Spot uses 8 - szDecimals. A size can have no more than szDecimals decimal places. Read szDecimals from the meta response and let the SDK apply the order-wire rules.
from decimal import Decimal oi_usd = Decimal(ctx["openInterest"]) * Decimal(ctx["markPx"])
Budget weighted HTTP and WebSocket limits
The documented IP allowance is 1,200 weighted HTTP units per minute. allMids, l2Book and several other common Info requests cost 2 units. Most other documented Info requests cost 20 units. An exchange request costs 1 + floor(batch length / 40).
The documented per-IP WebSocket limits allow 10 open connections and 30 new connections per minute. They also allow 1,000 subscriptions and 2,000 client-sent messages per minute across all connections. Cache shared public data, batch compatible actions and back off after HTTP 429 responses.
Frequently asked questions
Does the public Hyperliquid Info API require an API key?
Public market and state queries use the Info endpoint without a conventional API key. Trading actions still require the correct signed payload, and IP plus account limits still apply.
What are the main Hyperliquid API endpoints?
Mainnet HTTP uses https://api.hyperliquid.xyz and mainnet streaming uses wss://api.hyperliquid.xyz/ws. Testnet HTTP uses https://api.hyperliquid-testnet.xyz and testnet streaming uses wss://api.hyperliquid-testnet.xyz/ws. Append /info for reads and /exchange for signed actions. HyperEVM JSON-RPC is a separate API.
What should a WebSocket client do after reconnecting?
Resubscribe and wait for a fresh snapshot before serving state. Replace the local book from an l2Book snapshot. For time-series channels, reconcile the message marked isSnapshot: true and deduplicate stored events by their documented identity.