400 that names the offending field and, for precision mismatches, the exact normalized value to resubmit (see Errors). Preparing the order on your side is still worth doing — it saves the round-trip and gives you exact control over the values that trade — but it is now an optimization, not the only guard.
Prerequisites: an active VOOI API token and at least one connected exchange. See API Tokens and Use your own keys.
Server-side validation and normalizeParams
POST /exchange/orders accepts an optional boolean normalizeParams that controls what happens when your price or size doesn’t match the market’s tick or step:
normalizeParams: false(the default) — the order is rejected with a400whose per-fielderrorsarray includes the normalized value to resubmit, e.g.Size "5.1911" does not match the market step size (0.01). Use the normalized value: 5.19.normalizeParams: true— the API roundsprice,size, and trigger prices to the market’s rules for you and submits the normalized order.
The recipe
These five steps produce an order any venue accepts. Round the price before deriving size, so the size matches the price you’ll actually trade at. Do every calculation with an arbitrary-precision decimal type, never floating-point — rounding a price with a binary float reintroduces the exact errors these rules exist to prevent. The example under each step opens roughly $200 ofHYPE on Hyperliquid with a limit bid, using illustrative values from the market row (priceDecimals: 4, baseDecimals: 2, quoteDecimals: 2). Substitute the real values your row returns.
- Read the market row. Fetch
GET /exchange/marketsand find the(exchange, baseSymbol, quoteSymbol)row you’re trading. The constraint fields you need arepriceDecimals,baseDecimals,quoteDecimals, the currentprice, and the venue-specificdataextras (see Exchange-specific metadata). - Round the price down to the tick. For a limit order, round your chosen
pricedown topriceDecimalsdecimal places; for a market order, skip this and use the row’sprice. Hyperliquid has an extra rule — see Price and size precision below.
Example: you want to bid38.5274. That’s withinpriceDecimals(4), but Hyperliquid caps a price at 5 significant figures, so it becomes38.527. - Derive size from your notional. Floor your USD amount to
quoteDecimals, then divide by the price from step 2 — the rounded limit price, or the row’spricefor a market order. The result is the base-currencysize.
Example:200.00 ÷ 38.527 = 5.1911…HYPE. - Round the size down to the lot step. Round to the venue’s step (
data.stepSizeon Aster and Binance,data.tradingConfig.minOrderSizeChangeon Extended) or tobaseDecimalswhere no explicit step is published. If the size rounds to0, stop — the order is too small to represent and must not be submitted.
Example: round5.1911down tobaseDecimals(2) →5.19HYPE. - Check the minimum, then submit. Confirm the order clears the venue’s minimum (see Minimum order size), then send
POST /exchange/orders.
Example:5.19 × 38.527 ≈ \$200, well above Hyperliquid’s $10 minimum. Submitprice: "38.527",size: "5.19".
Price and size precision
Every venue enforces a price tick and a size step, and the API applies them uniformly: withnormalizeParams: false (the default) an off-tick price or off-step size is rejected with a 400 naming the field and the normalized value; with normalizeParams: true both are rounded for you. The per-market values live on the /exchange/markets row — priceDecimals/baseDecimals everywhere, plus explicit steps in the venue-specific data (data.stepSize on Aster and Binance, data.tradingConfig.minPriceChange/minOrderSizeChange on Extended, data.qtyStep on Bybit, data.baseIncrement/data.quoteIncrement on Ondo).
The safe default is still to round both price and size yourself for every venue — it always produces a valid order and no round-trip is wasted.
Hyperliquid prices carry an extra rule. Beyond
priceDecimals, a Hyperliquid price may use at most 5 significant figures, and at most max(6 − baseDecimals, 0) decimal places. Integer prices are always allowed regardless of significant figures. A price that breaks either limit is rejected, not snapped — for example 12345.6 (6 significant figures) is invalid, while 12345 and 1234.5 are fine. Hyperliquid treats the row’s baseDecimals as its szDecimals for these calculations. A too-precise Hyperliquid price or size is caught before the venue and comes back with a readable precision message stating which decimals or significant-figure limit it broke.Extended rounds to a price step, not just decimals. Extended publishes a price tick as
data.tradingConfig.minPriceChange. When that tick isn’t a power of ten (e.g. 0.5), rounding only to priceDecimals leaves the price off-tick. Round the price down to the minPriceChange step, the same way you round size to minOrderSizeChange. Extended prices are forwarded to the venue as you send them, so the snapping has to happen on your side.Minimum order size
An undersized order is rejected with a readable400 — e.g. Order must have minimum value of $10 on Hyperliquid, or Order notional <x> is below the market minimum <y> on Lighter — regardless of the normalizeParams setting (minimums are never auto-fixed). Each venue defines its own minimum, and what the market row exposes about it differs per exchange:
The per-symbol values on Aster, Binance, Extended, Bybit, MEXC, and Lighter are read from the live market row and can change — always read them from the current
GET /exchange/markets response rather than hard-coding a number. The Hyperliquid $10 figure is a fixed platform rule.Closing a dust position. Setting
reduceOnly: true waives the minimum entirely on Hyperliquid, Lighter, Robinhood, Aster, Extended, Binance, and Bybit, so a position left below the venue minimum can still be closed in full. MEXC is the exception — its minimum applies to reduce-only orders too.Worked example: a $5 order on Hyperliquid
A $5 long onHYPE falls below Hyperliquid’s $10 minimum order value. The API rejects it before it reaches the venue with a readable 400:
Putting it in code
This prepares an order for any venue: round the price, derive the size from your notional, round the size to the lot step, and check the minimums — all before the order reaches the venue. Themarket object is built from the /exchange/markets row, with the per-venue lot step and minimums read from its data field. Use an arbitrary-precision decimal type throughout; the same logic ports across languages.
Read the market row (public, no authentication required):
market fields from the row’s data per venue:
- Aster and Binance —
stepSizeandminSizefrom theLOT_SIZEfilter,minNotionalfrom theMIN_NOTIONALfilter (both underdata.filters). - Extended —
stepSize=data.tradingConfig.minOrderSizeChange,minSize=data.tradingConfig.minOrderSize. - Hyperliquid — omit
stepSizeandminSize; setminNotionalto"10". - Lighter and Robinhood — omit
stepSizeandminSize(no minimum is published on the row); setminNotionalto your blanket floor.
When an order is still rejected
If a prepared order is rejected, parameter problems come back as structured400s with a per-field errors array; genuinely venue-side rejections (insufficient margin, would-not-reduce, and similar) come back as 503, and how readable those are depends on the venue. See Errors for the status codes, the response shapes, and which venues return the venue’s own reason. As a rule, confirm fills through order status or the real-time stream rather than trusting the submit response alone.
Next steps
Orders
The full order request: types, brackets, batch, and cancel
Errors
Status codes and how venue rejections are surfaced