Trading API
Limit, trigger and market orders, closes, cancels, modifies, leverage and the dead man's switch.
Last updated
All trading endpoints take JSON and, on a managed account, return the exchange's result directly. Batch endpoints accept up to 50 items per request.
Markets and numbers
- Perpetuals by name:
BTC,ETH. - HIP-3 perpetuals with their dex prefix:
xyz:TSLA. See HIP-3 markets. - Spot by pair (
PURR/USDC) or by Hyperliquid spot id (@107).
Send prices (px, trigger_px) and sizes (sz) as decimal strings, such as "2500.5". Numbers work too, but strings avoid floating-point surprises. Sizes are in the base asset (0.01 ETH, not $0.01).
Limit and trigger prices are sent as given, never rounded, so they must be on the market's tick: at most 5 significant figures (whole numbers are always fine), and at most 6 decimals minus the market's size decimals (8 for spot). Sizes may have at most the market's size decimals (szDecimals in GET /v1/market/meta). The API checks both before sending, and a request with a price or size off the grid returns 400 naming the order, such as orders[1].px 2500.15 is off ETH's tick. Nothing in that request is sent. Market orders and closes are rounded for you. An unknown coin returns 400 with the code unknown_asset.
Every order must be worth at least $10 (price × size). That includes reduce-only orders and partial closes: close the whole position if what's left is under $10.
Limit orders
curl -s -X POST https://hl-api-production-65c8.up.railway.app/v1/orders \
-H "Authorization: Bearer $API_KEY" \
-H 'content-type: application/json' \
-d '{
"orders": [{
"coin": "ETH",
"side": "buy",
"px": "2500",
"sz": "0.01",
"type": { "limit": { "tif": "Gtc" } },
"reduce_only": false,
"cloid": "7f1c1d2e-4b5a-4c3d-9e8f-0a1b2c3d4e5f"
}]
}'Field | Required | Notes |
|---|---|---|
| Yes | Market name, as above |
| Yes |
|
| Yes | Limit price |
| Yes | Size in the base asset |
| No | Order type, below. Default: good-til-cancelled limit |
| No | Only ever reduce a position. Default |
| No | Your client order id, a UUID. Always set one |
The time in force (tif) picks how a limit order behaves:
| Behaviour |
|---|---|
| Good til cancelled. Fills what it can, rests the rest on the book. |
| Add liquidity only (post-only). Rejected instead of filling if it would cross the book. |
| Immediate or cancel. Fills what it can at once, cancels the rest. |
Send several orders in one orders array to place them together.
Take profit and stop loss
A trigger order waits until the market reaches trigger_px, then executes. Set tpsl to "tp" for a take profit or "sl" for a stop loss, and use reduce_only so it can only close your position:
{
"orders": [
{
"coin": "ETH",
"side": "sell",
"px": "2200",
"sz": "0.01",
"reduce_only": true,
"type": {
"trigger": { "trigger_px": "2250", "is_market": true, "tpsl": "sl" }
}
},
{
"coin": "ETH",
"side": "sell",
"px": "3000",
"sz": "0.01",
"reduce_only": true,
"type": {
"trigger": { "trigger_px": "3000", "is_market": false, "tpsl": "tp" }
}
}
]
}With is_market: true the order executes as a market order once triggered; px is the worst price you will accept. With is_market: false it becomes a limit order at px. An accepted trigger order's result is resting, with its oid, like a limit order on the book; it shows in open orders until it fires.
Market orders
curl -s -X POST https://hl-api-production-65c8.up.railway.app/v1/orders/market \
-H "Authorization: Bearer $API_KEY" \
-H 'content-type: application/json' \
-d '{ "coin": "BTC", "side": "sell", "sz": "0.001", "slippage_bps": 100 }'Hyperliquid has no true market order. The API sends an immediate-or-cancel limit order at the mid price plus or minus slippage_bps (default 50, so 0.5%), rounded to a valid price and size. Anything that can't fill within that price is cancelled, so check total_sz in the result. reduce_only and cloid work as for limit orders.
Closing a position
curl -s -X POST https://hl-api-production-65c8.up.railway.app/v1/orders/close \
-H "Authorization: Bearer $API_KEY" \
-H 'content-type: application/json' \
-d '{ "coin": "ETH", "slippage_bps": 50 }'A reduce-only market order against your current perpetual position, in the right direction. Pass sz to close part of it; omit it to close all of it. An sz larger than the position closes the whole position, without an error. Returns 400 if you have no position in that market. A partial close under $10 is rejected, like any order.
Cancelling
By Hyperliquid order id:
{
"cancels": [
{ "coin": "ETH", "oid": 123456789 },
{ "coin": "BTC", "oid": 987654321 }
]
}Or by your client order id:
{
"cancels": [
{ "coin": "ETH", "cloid": "7f1c1d2e-4b5a-4c3d-9e8f-0a1b2c3d4e5f" }
]
}Send either to POST /v1/orders/cancel. Use one kind of id per request.
Modifying
POST /v1/orders/modify replaces a resting order's price, size or type in one step, without a separate cancel. Name the order by its cloid or its oid (exactly one), and send the full new order:
{
"modifies": [
{
"cloid": "7f1c1d2e-4b5a-4c3d-9e8f-0a1b2c3d4e5f",
"order": {
"coin": "ETH",
"side": "buy",
"px": "2490",
"sz": "0.02",
"type": { "limit": { "tif": "Alo" } }
}
}
]
}An order modified by cloid keeps that cloid. One modified by oid keeps only the cloid you put in order: leave it out and the order loses its client order id.
Leverage
Leverage is set per market, along with the margin mode:
curl -s -X POST https://hl-api-production-65c8.up.railway.app/v1/leverage \
-H "Authorization: Bearer $API_KEY" \
-H 'content-type: application/json' \
-d '{ "coin": "ETH", "leverage": 5, "is_cross": true }'- Cross (
is_cross: true): the position draws on your whole account balance as margin. - Isolated (
is_cross: false): the position's margin is ring-fenced, so a liquidation can only lose what is assigned to it.
Some HIP-3 markets are isolated-only; see HIP-3 markets.
Dead man's switch
POST /v1/schedule-cancel cancels all your open orders at a time you choose (unix milliseconds) unless you push it back first:
{ "time": 1790496000000 }Call it regularly with a time a little in the future. If your systems stop running, the deadline passes and every open order is cancelled. Send {} to clear the schedule. The time must be at least 5 seconds away.
Hyperliquid only offers this to accounts that have traded at least $1M in volume; below that, the request is rejected with 422.
Client order ids
Set a cloid (a UUID you generate) on every order. It lets you:
- cancel by
cloidwithout waiting for theoid; - look the order up with
GET /v1/orders/{cloid}; - resolve an ambiguous submit, below.
Use a new cloid for every order. A request that repeats a cloid among its own orders is rejected with 400, but a cloid already used by an earlier order isn't: two live orders can then share it, and cancelling, modifying or looking up by that cloid can't tell them apart.
The response
On a managed account every trading endpoint returns the exchange's result:
{
"id": "5fcab0cf-45f1-43e3-b02f-b06c05409445",
"kind": "order",
"status": "submitted",
"outcome": "partial",
"response": {
"results": [
{
"status": "filled",
"oid": 123456790,
"total_sz": "0.01",
"avg_px": "2501.2"
},
{ "status": "error", "message": "Insufficient margin to place order." }
]
},
"submitted_at": "2026-09-27T07:12:03.120Z",
"submit_latency_ms": 118
}outcome summarises the batch: ok (every item succeeded), partial (some items errored) or rejected (all of them errored). response.results has one entry per order, cancel or modify, in the order you sent them, so you can match them up by position. When Hyperliquid refuses the whole batch instead, the response is a 422, never a 200 with fewer results:
| Meaning | Extra fields |
|---|---|---|
| On the book, or a trigger order waiting |
|
| Filled immediately |
|
| Accepted, not yet on the book | |
| Cancel, modify or leverage change done | |
| This item failed |
|
A 200 can still contain errors: always check each result, not just the status code.
Handling a 502
The status code tells you whether the request reached the exchange:
Status | Meaning | What to do |
|---|---|---|
| Submitted. Check each result. | Nothing more |
| Hyperliquid rejected the whole request. | Fix the cause; don't retry blindly |
| The call to Hyperliquid failed in transit. The action may or may not have executed. | Check before retrying |
After a 502, never resend blindly: you could double your position. Look the order up by its cloid with GET /v1/orders/{cloid} (or list open orders and recent fills).
A lookup right after placing an order can answer unknownOid for an order that is live: Hyperliquid takes a moment to index it. Treat unknownOid as "not known yet", not "not placed". Wait a second and look again, a few times over several seconds, and only send the order again once it still isn't found. Resending with the same cloid doesn't protect you, since a cloid used by an earlier order isn't rejected.