Skip to main content
Each venue enforces its own tick, lot, and minimum rules. The API checks your order against them before it reaches the venue: an invalid value is rejected with a 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 a 400 whose per-field errors array 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 rounds price, size, and trigger prices to the market’s rules for you and submits the normalized order.
Minimums, maximums, notional limits, and price bands are hard errors either way — they are never auto-fixed. Keep the flag off when you need to know exactly what values will trade; turn it on when a best-effort fit is acceptable.

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 of HYPE 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.
  1. Read the market row. Fetch GET /exchange/markets and find the (exchange, baseSymbol, quoteSymbol) row you’re trading. The constraint fields you need are priceDecimals, baseDecimals, quoteDecimals, the current price, and the venue-specific data extras (see Exchange-specific metadata).
  2. Round the price down to the tick. For a limit order, round your chosen price down to priceDecimals decimal places; for a market order, skip this and use the row’s price. Hyperliquid has an extra rule — see Price and size precision below.
    Example: you want to bid 38.5274. That’s within priceDecimals (4), but Hyperliquid caps a price at 5 significant figures, so it becomes 38.527.
  3. 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’s price for a market order. The result is the base-currency size.
    Example: 200.00 ÷ 38.527 = 5.1911… HYPE.
  4. Round the size down to the lot step. Round to the venue’s step (data.stepSize on Aster and Binance, data.tradingConfig.minOrderSizeChange on Extended) or to baseDecimals where no explicit step is published. If the size rounds to 0, stop — the order is too small to represent and must not be submitted.
    Example: round 5.1911 down to baseDecimals (2) → 5.19 HYPE.
  5. 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. Submit price: "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: with normalizeParams: 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 readable 400 — 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.
A pragmatic shortcut many clients use is a single blanket floor — refuse any order below roughly $10 of notional before it ever reaches a venue. That one guard covers Hyperliquid’s $10 rule and is comfortably above the typical Aster and Extended minimums, so it prevents the most common “too small” rejection without per-venue logic. Use the per-venue values above when you need to trade closer to the true minimum.

Worked example: a $5 order on Hyperliquid

A $5 long on HYPE falls below Hyperliquid’s $10 minimum order value. The API rejects it before it reaches the venue with a readable 400:
Preparing the order catches this even earlier, without a round-trip. At step 5 you compare the $5 notional against Hyperliquid’s $10 minimum, see that it falls short, and either raise the size to clear $10 or stop the order with a clear message of your own.

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. The market 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):
Round and size the order from the fields on that row:
Submit the prepared values:
Build the optional market fields from the row’s data per venue:
  • Aster and Binance — stepSize and minSize from the LOT_SIZE filter, minNotional from the MIN_NOTIONAL filter (both under data.filters).
  • Extended — stepSize = data.tradingConfig.minOrderSizeChange, minSize = data.tradingConfig.minOrderSize.
  • Hyperliquid — omit stepSize and minSize; set minNotional to "10".
  • Lighter and Robinhood — omit stepSize and minSize (no minimum is published on the row); set minNotional to your blanket floor.

When an order is still rejected

If a prepared order is rejected, parameter problems come back as structured 400s 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