Skip to main content
All order endpoints accept an exchange field in the body (for writes) or an exchanges query parameter (for reads) to target a specific venue.
Prerequisites: an active VOOI API token and at least one connected exchange. See API Tokens and Use your own keys if you don’t have them yet.

Place an order

POST /exchange/orders — create a limit, market, trigger, or bracket order. Authentication is the standard Authorization: Bearer <token> header; you do not sign individual orders with the Ed25519 wallet key. The on-venue signatures needed to submit an order are produced server-side from the credentials attached when you connected the exchange. Behavioral quirks worth knowing up front:
  • The presence of price decides market vs. limit: supply price for a limit order, omit it for a market order. timeInForce applies only to limit orders — sending it on a price-less (market) order is rejected with a 400 validation error (timeInForce is only applicable to limit orders). Extended is the exception only for ioc: a market order there accepts ioc (or no timeInForce) but rejects gtc/alo/fok. The safe pattern for a market order is to omit both price and timeInForce: send { asset, exchange, side, size }.
  • size is in base currency as a decimal string (e.g. "0.001") and price is a decimal string too. Round both to the venue’s tick, lot, and minimum rules before submitting, or set normalizeParams: true to have the API round price and size for you — see Preparing orders for the per-exchange rules and how validation behaves with and without the flag.
  • The optional broker field is venue-specific. The venues read broker.id and broker.feeBps, but interpret them differently:
    • Hyperliquid — on-order builder address and fee.
    • Lighter and Robinhood — integrator account index and fee (applied to both taker and maker).
    • Aster — builder address and fee. feeBps must not exceed the cap approved at registration — see Customize the builder approval.
    • Extended — the broker’s builder fee is applied to the order via the broker’s Extended builder code; no separate per-user approval is needed.
    • Binance — attribution is tag-based and carries no fee; see Configure exchange defaults.
    • MEXC — broker.id is the Broker_ID MEXC issued, used to attribute the order. feeBps is still required by the schema but has no effect, because MEXC charges no builder fee.
    If the broker that registered the user has configured per-exchange defaults via POST /broker/exchanges, those defaults override what you send on the order. The broker record carries two rates: orders the venue executes as limit take limitFeeBps, orders it executes as market take marketFeeBps (a trigger order counts as market on Hyperliquid, Aster, and Ondo), and Lighter and Robinhood receive the two as their maker and taker fees. Omit broker to skip builder / integrator fees when no broker default is set.
  • reduceOnly: true exempts the order from the venue’s minimum size and minimum notional checks on Hyperliquid, Lighter, Robinhood, Aster, Extended, Binance, and Bybit, so a position smaller than the venue minimum can still be closed in full. MEXC still applies its minimum to reduce-only orders, and Ondo enforces no minimum size at all. Every other rule — tick, lot step, price bands — still applies everywhere.
  • takeProfit and stopLoss (bracket orders) are supported on Hyperliquid, Lighter, Robinhood, Extended, Bybit, MEXC, and Ondo; they are not supported on Aster or Binance — use a standalone trigger order there instead. On Extended, a single bracket appears as two orders in open-orders and history — the parent orderId suffixed with :tp and :sl. Canceling either leg cancels both, and each leg closes the entire position (position mode), so the per-leg size is effectively ignored. Lighter and Robinhood bracket child orders are likewise position-tied — the per-leg size is not honored there either.
  • timeInForce: "fok" is available on Aster and, per market, on Binance (accepted only where the market lists it).
  • trigger cannot be combined with takeProfit or stopLoss on the same order.

Venue capability matrix

Robinhood runs on Lighter’s infrastructure — every Lighter column above applies to Robinhood as well. On Bybit, a bracket leg’s size must equal the parent order’s size; a differing per-leg size is rejected. On Ondo, a market order accepts only ioc (or no timeInForce), and clientOrderId must be 1–64 characters of letters, digits, underscores, or dashes. Validation errors that surface these constraints are returned as 400 Bad Request with a per-field errors array naming what was rejected; an order the venue itself rejects comes back as 503. See Errors for the full status-code reference and how each venue’s rejection reason is surfaced.

Batch create

POST /exchange/batch-orders — create several orders in a single request. The response results array is index-correlated with the input — position i tells you whether input order i was created or rejected. Batch create is not available on Extended or MEXC — it returns 400 with <exchange>: batchCreateOrders is not supported. Batch cancel is likewise unavailable on Ondo. On Hyperliquid, bracket takeProfit / stopLoss must be sent via a single POST /exchange/orders — batch orders carrying takeProfit or stopLoss are rejected.

Cancel an order

Open orders

GET /exchange/open-orders — list your currently open orders. Each order carries filledSize, the size already executed in base-asset units; it is null when the venue does not report it, and on orders placed before the field existed. Compare it against size to render fill progress, and expect status: "partiallyFilled" in between. Each order also carries a type and a status; the response schema lists the full sets. A few values are non-obvious: stopLossLimit and takeProfitLimit are limit orders that activate at a trigger price; liquidation is a venue-generated closure, not a user-placed order; other covers exchange-specific variants that don’t map to the standard set (e.g. trailing-stop-market on Aster, TWAP on Lighter).

Look up a single order

GET /exchange/order — fetch one order by the venue’s orderId or your own clientOrderId. Pass exactly one of the two; supplying both, or neither, is a 400. The response is a single order row (same shape as GET /exchange/open-orders entries); an unknown order returns 404.
  • exchange is required.
  • asset is required on Aster, Binance, and Bybit (the lookup is scoped to one market there); it is not needed on Hyperliquid, Extended, MEXC, or Ondo.
  • On Hyperliquid, a clientOrderId lookup expects the venue’s client-order-id format — a 0x-prefixed 128-bit hex string.
  • Lighter and Robinhood do not support single-order lookup — the request returns a 400 not-supported error. Use GET /exchange/open-orders or order history instead.

Bybit trading agreements

Bybit requires a one-time agreement before it accepts any order on a traditional-asset market. There are two: one covering stocks, ETFs, and metals, and one covering crude oil (BZ, CL). Crypto markets need no agreement. POST /exchange/bybit/agreement takes { asset } — the Bybit market whose agreement you are accepting. Bybit offers no way to ask whether an agreement is already accepted, so calling it again is a no-op; accept it before the first order on such a market, or in response to a rejection. Until the agreement is accepted, orders on those markets are rejected by the venue. Two failure paths are worth handling separately:
  • 400 — asset matched more than one Bybit market and needs its full pair name, or the connected key cannot sign the agreement. Bybit accepts the signature only from a master-account key carrying Account Transfer, Subaccount Transfer, or Withdrawal permission — none of which connecting a Bybit key otherwise requires.
  • 404 — Bybit is not connected for this user, or asset matches no Bybit market.

Order books, quotes, and public tape

Next steps

History

Paginated order and trade history

Positions

Positions, balances, and leverage