Architecture: info, exchange, WebSocket
Hyperliquid's API has three surfaces: an unsigned read endpoint, a signed write endpoint, and a WebSocket that streams data and can carry both.
Your code reads and changes Hyperliquid through this API. Every balance, position and resting order is HyperCore replicated state. Anyone may read any account; only the owner, or a key it approved, may change it.
Three surfaces behind one API server
Section titled “Three surfaces behind one API server”Your requests go to an API server, which keeps a copy of a node’s state. Data freshness is covered on API servers.
| Surface | Path | What it does | Signed |
|---|---|---|---|
| Info | POST /info |
Reads markets, books, balances, positions, orders and fills, for any address | No |
| Exchange | POST /exchange |
Writes: orders, cancels, transfers, approvals, deployments | Yes |
| WebSocket | /ws |
Streams subscriptions; can also carry info requests and signed actions as post messages |
Only the actions it carries |
Every HTTP request is a POST with a JSON body and Content-Type: application/json. There are no per-resource URLs: a type field picks the query or action.
Think of /info as a public records office and /exchange as a counter for signed instructions. Unlike a counter, a write waits for consensus; a read never does.
The numbers
Section titled “The numbers”| Mainnet | Testnet | |
|---|---|---|
| REST base | https://api.hyperliquid.xyz |
https://api.hyperliquid-testnet.xyz |
| Reads | <base>/info |
<base>/info |
| Writes | <base>/exchange |
<base>/exchange |
| WebSocket | wss://api.hyperliquid.xyz/ws |
wss://api.hyperliquid-testnet.xyz/ws |
| Case | Shape |
|---|---|
| Info request | The requested JSON itself, with no envelope |
| Exchange action with a result | {"status":"ok","response":{"type":"order","data":{"statuses":[...]}}} |
| Exchange action with nothing to report | {"status":"ok","response":{"type":"default"}} |
| Exchange action refused | {"status":"err","response":"<plain string>"} |
| Body that does not parse | HTTP 422 with a plain-text body beginning Failed to deserialize |
One IP’s REST requests share a weight budget of 1200 per minute; actions also draw on a per-address budget. See rate limits.
Every write is one signed JSON body
Section titled “Every write is one signed JSON body”Here is an order as your code sends it:
{ "action": { "type": "order", "orders": [{ "a": 0, "b": true, "p": "50000", "s": "0.001", "r": false, "t": { "limit": { "tif": "Gtc" } } }], "grouping": "na" }, "nonce": 1713825891591, "signature": { "r": "0x...", "s": "0x...", "v": 27 }, "vaultAddress": "0x...", "expiresAfter": 1713825951591}vaultAddress and expiresAfter are optional and exist only for L1 actions, one of two kinds:
| L1 action | User-signed action | |
|---|---|---|
| Examples | order, cancel, modify, scheduleCancel, updateLeverage, vaultTransfer, twapOrder |
usdSend, spotSend, sendAsset, withdraw3, usdClassTransfer, approveAgent, approveBuilderFee |
| What is signed | a hash of the action’s msgpack bytes, the nonce and the optional fields | the action’s fields as readable EIP-712 typed data |
| EIP-712 domain | Exchange, chain id 1337 |
HyperliquidSignTransaction, chain id from signatureChainId |
| Network bound by | source: "a" mainnet, "b" testnet |
hyperliquidChain: "Mainnet" or "Testnet" |
| Who may sign | the account, or an API wallet it approved | the account’s own key |
| Optional body fields | vaultAddress, expiresAfter |
none |
Sub-accounts and vaults have no keys: the master account signs with vaultAddress set to the sub-account or vault. Byte-level detail is on signing.
Read every response defensively
Section titled “Read every response defensively”"status":"ok"doesn’t mean every order worked: each gets its ownresting,filledor{"error": "..."}. The docs’okexample carries"Order must have minimum value of $10.".- Read the body as text first: a 422 is plain text.
- Query by the account’s address; an API wallet’s address returns nothing.
One WebSocket for streams and posts
Section titled “One WebSocket for streams and posts”One connection carries three messages; details are on WebSocket.
| Message | Use |
|---|---|
{"method":"subscribe", "subscription": {...}} |
streams |
{"method":"ping"} |
liveness |
{"method":"post","id":N,"request":{"type":...,"payload":{...}}} |
anything HTTP can send; type is "info" or "action" |
Replies to a post arrive on the post channel with your id, wrapped one level deeper than over HTTP. API servers drop WebSockets without notice and don’t replay posts: reconcile them over HTTP.
Test on the network you ship to
Section titled “Test on the network you ship to”- The networks can parse shapes differently: they diverged while the HIP-4 deploy moved from
{"type":"spotDeploy","outcome":{...}}to top-leveloutcomeDeploy. - Testnet is separate state: asset ids, token indices, HIP-3 dexes and API wallet approvals differ. Key caches by network.
For your first unsigned read, see Your first request. For how both kinds of action are signed, see Signing from first principles.