Error responses
Hyperliquid reports errors at four layers, from HTTP status to a string inside a successful response, and several name a cause other than the real one.
A client that reads only one layer treats real rejections as success. A reply can be HTTP 200 with "status": "ok" and still reject every order.
Say you send 3 orders in one batch and margin runs out. The reply is still "status": "ok", and one of the 3 statuses is {"error": "Insufficient margin to place order."}. Stop at the status and you’d think all 3 are live.
Several errors name the wrong cause: a serialisation bug arrives as an account that “does not exist”, a trailing zero as a tick-size error.
Where an error can appear
Section titled “Where an error can appear”| Layer | Shape | Example |
|---|---|---|
| HTTP status | 422 with a body that may be plain text, not JSON | Failed to deserialize ... |
| HTTP status | 429 when the IP’s weight budget is spent | no Retry-After header |
| Whole action | {"status": "err", "response": "<string>"} |
Price would cross book on a modify |
| Whole batch | one error for the batch, from pre-validation | an empty batch of orders |
| Per order | "statuses": [{"error": "<string>"}] inside "status": "ok" |
Insufficient margin to place order. |
| WebSocket post | "response": {"type": "error", "payload": "<string>"} |
mirrors the HTTP status and description |
| From the HyperEVM | nothing: the EVM transaction succeeds | read the L1 record (data) |
Order and cancel errors normally return a list as long as the batch. Payload-only errors fail pre-validation with one error for the whole request (an empty batch, a TP/SL order that is not reduce-only, an order too far from the reference price, some tick-size checks); the docs recommend copying it to every order before your callbacks.
A CoreWriter failure leaves the money where it was; its only record is the typed L1 action’s error field from the explorer’s userDetails (footguns).
Errors by real cause
Section titled “Errors by real cause”Shape: the payload isn’t what the server expects
Section titled “Shape: the payload isn’t what the server expects”| Error | Cause and fix |
|---|---|
HTTP 422 Failed to deserialize |
Wrong wire shape. A catch-all: a missing key, a wrong type, an extra key. Read the body as text before parsing it as JSON |
a rejection of f: false or a: false |
Cancels and modifies hashed with the flag set to false are refused. Omit the key |
| a 422 on a missing nullable key | Some actions need an explicit null rather than an absent key, for example the nullable fields of HIP-3 registerAsset2 |
Invalid token |
sendAsset to the HyperEVM with dex ""; use "spot" |
frame on WebSocket channel error |
the subscription type is dead, for example webData2 |
The server parses before checking the signature, so a deliberately wrong signature probes a shape: a 422 means it does not parse, a signer error means it does (the probe).
Signature: the server recovered someone else’s address
Section titled “Signature: the server recovered someone else’s address”| Error | Cause and fix |
|---|---|
L1 error: User or API Wallet 0x... does not exist. |
The signed bytes differ from what the server rebuilt: key order, trailing zeros, an uppercase address, or the wrong signing scheme. The address in the message changes with the input |
Must deposit before performing actions. User: 0x... |
Either the same recovery failure, or the address genuinely holds nothing. If the address in the message is not yours, it is a signing bug |
Price must be divisible by tick size. |
Usually precision, but a price string signed with trailing zeros has produced it too |
Invalid multi-sig outer signer |
Inner r and s sent with leading zeros; about 1 in 4 submissions failed before stripping them (multi-sig actions) |
The docs warn that recovering the signer locally proves nothing: your local payload may be what is wrong (signing). In multi-sig actions, a checksummed address recovers the wrong signer and the error blames the outer signer.
Account state: valid, but not possible now
Section titled “Account state: valid, but not possible now”| Error | Meaning |
|---|---|
Insufficient margin to place order. |
not enough margin for the new order |
Reduce only order would increase position. |
the reduce-only order would increase the position |
Order has insufficient spot balance to trade |
spot only: not enough balance of the token the order spends |
Order would cause position to exceed margin tier limit at current leverage |
lower leverage or size (margin) |
Order was never placed, already canceled, or filled. |
the cancel lost a race with a fill or an earlier cancel |
TWAP was never placed, already canceled, or filled. |
the same, for twapCancel |
Insufficient USDC or HYPE balance for token transfer gas |
a transfer to the HyperEVM left nothing to pay its gas |
Borrow/Lend error: Insufficient spot balance for operation |
a supply larger than the spot balance; measured on testnet |
Precision: a number the market won’t accept
Section titled “Precision: a number the market won’t accept”The rules are on precision.
| Error | Meaning |
|---|---|
Price must be divisible by tick size. |
price off the market’s tick grid |
Order must have minimum value of $10. |
perp order below the minimum notional |
Order must have minimum value of 10 {quote_token}. |
spot order below the minimum, in the quote token |
Invalid number of decimals: 9599.79902008 |
an amount the destination cannot represent, such as USDC to the HyperEVM not in whole 1e-6 units (5 of 5 measured) |
Invalid TWAP duration: 1 min(s) |
a TWAP duration outside the allowed range, here 1 minute |
Limits: the book, a price band, a cap or a rate limit
Section titled “Limits: the book, a price band, a cap or a rate limit”| Error | Meaning |
|---|---|
Post only order would have immediately matched, bbo was {bbo}. |
an Alo order would have crossed |
Order could not immediately match against any resting orders. |
an Ioc order found nothing to fill |
No liquidity available for market order. |
a market order found an empty book |
Order price too far from oracle |
the limit price is too far from the oracle price |
Invalid TP/SL price. |
the trigger price was rejected |
Order would increase open interest while open interest is capped |
the asset is at its open interest cap; two error types share this string |
Order rejected due to price more aggressive than oracle while at open interest cap |
at the cap, an order priced more aggressively than the oracle is refused |
Order would increase open interest too quickly |
open interest is growing faster than allowed |
Price would cross book |
a modify whose new price would execute |
| HTTP 429 | IP weight exhausted (rate limits) |
too many pending post requests id=N |
WebSocket posts backed up; keep under 100 in flight |
Two limits have no documented string: the address action budget and the 1000-open-order cap on reduce-only and trigger orders (values in the numbers). Historical statuses tell apart the two causes sharing the open interest cap string: positionIncreaseAtOpenInterestCapRejected and positionFlipAtOpenInterestCapRejected.
Mode: the account’s mode forbids it
Section titled “Mode: the account’s mode forbids it”| Error | Meaning |
|---|---|
Action disabled when unified account is active |
usdSend, spotSend or usdClassTransfer on a unified or portfolio margin account; use sendAsset (transfers) |
Manual borrow/lend not allowed with portfolio margin |
a manual borrow/lend operation on a portfolio margin account; observed on mainnet for withdraws sent through CoreWriter |
Portfolio margin requires account value of $10000 or total volume of $5000000 |
the account is not eligible for portfolio margin |
Abstraction transition not allowed |
a mode switch refused; observed leaving unified mode, cause unresolved |
One mode rule has no documented string: a builder address must be in standard mode to accrue builder fees (account modes).
After the fact: statuses in order history
Section titled “After the fact: statuses in order history”orderStatus and historical orders record each outcome as a status string:
| Kind | Statuses |
|---|---|
| Rejections, mirroring the errors above | tickRejected, minTradeNtlRejected, perpMarginRejected, badAloPxRejected, iocCancelRejected, oracleRejected, and so on |
| Cancellations the protocol made for you | marginCanceled, selfTradeCanceled, reduceOnlyCanceled, siblingFilledCanceled, openInterestCapCanceled, liquidatedCanceled, delistedCanceled, scheduledCancel |
The numbers
Section titled “The numbers”| Threshold behind an error | Value |
|---|---|
| Minimum perp order | $10 |
| Minimum spot order | 10 units of the quote token |
scheduleCancel lead time |
at least 5 s |
| Minimum TWAP duration | 5 minutes |
| Open orders before reduce-only and trigger orders are refused | 1000 |
| WebSocket posts in flight | 100 |
| Unified and portfolio margin actions | 50k per day |
| Rate-limited address | 1 action per 10 s |
Handling a response and deciding on a retry
Section titled “Handling a response and deciding on a retry”Handle a response in this order: HTTP status, body as text, JSON parse, top-level status, every element of statuses. Then decide on a retry:
| Cause | Retry unchanged? | What to do |
|---|---|---|
| Shape | never | fix the payload; probe with a dummy signature |
| Signature | never | compare your bytes with a reference SDK |
| Account state | after the state changes | add margin, reduce size, refresh positions |
| Precision | never | round to the market’s rules before signing |
| Limits | after backoff or a price change | back off on 429; reprice on band and cap errors |
| Mode | never | change the action or the account mode |
| Timeout, 5xx | unknown outcome | reconcile by cloid first; never resend blindly |
A blind retry after a timeout can double an order or transfer: check orderStatus by cloid, or the ledger for transfers, first. A successful modify can return a bare {"status": "ok"} with no statuses: treat it as success and look up the new oid by cloid.
For every string in one table, see the error catalogue. To turn a signer error into a fix, see Debugging signatures and wire shapes.