> ## Documentation Index
> Fetch the complete documentation index at: https://perps-api.vooi.io/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Orders

> Place, cancel, and list orders across every connected exchange

All order endpoints accept an `exchange` field in the body (for writes) or an `exchanges` query parameter (for reads) to target a specific venue.

<Info>
  **Prerequisites:** an active VOOI API token and at least one connected exchange. See [API Tokens](/docs/tokens) and [Use your own keys](/docs/exchanges) if you don't have them yet.
</Info>

## Place an order

[`POST /exchange/orders`](/docs/api-reference/exchange/create-a-new-order) — 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](/docs/trading-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](/docs/register-aster#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](/docs/broker#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`](/docs/broker#configure-exchange-defaults), 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

| Capability | Hyperliquid | Lighter | Aster | Extended | Binance | Bybit | MEXC | Ondo |
| - | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: |
| Market, limit | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Stop-loss / take-profit via `trigger` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Bracket `takeProfit` / `stopLoss` on a parent order | ✓ | ✓ | ✗ | ✓ | ✗ | ✓ | ✓ | ✓ |
| `reduceOnly` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| `timeInForce: "gtc"` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| `timeInForce: "ioc"` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| `timeInForce: "fok"` | ✗ | ✗ | ✓ | ✗ | per-market | ✓ | ✓ | ✗ |
| `timeInForce: "alo"` (post-only) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Batch create | ✓ | ✓ | ✓ | ✗ | ✓ | ✓ | ✗ | ✓ |
| Batch cancel | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✗ |
| [`DELETE /exchange/all-orders`](/docs/api-reference/exchange/cancel-all-open-orders-on-an-exchange-supported-on-binance-extended-and-lighter) | ✗ | ✓ | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ |

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](/docs/errors) for the full status-code reference and how each venue's rejection reason is surfaced.

### Batch create

[`POST /exchange/batch-orders`](/docs/api-reference/exchange/create-multiple-orders-in-a-single-batch) — 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`](/docs/api-reference/exchange/create-a-new-order) — batch orders carrying `takeProfit` or `stopLoss` are rejected.

## Cancel an order

* [`DELETE /exchange/orders`](/docs/api-reference/exchange/cancel-an-open-order) — cancel one order by `orderId`.
* [`DELETE /exchange/batch-orders`](/docs/api-reference/exchange/cancel-multiple-orders-in-a-single-batch) — cancel several at once; same index-correlated response as batch create.
* [`DELETE /exchange/all-orders`](/docs/api-reference/exchange/cancel-all-open-orders-on-an-exchange-supported-on-binance-extended-and-lighter) — cancel every open order on an exchange. Supported on **Binance, Lighter, Robinhood, Extended, Bybit, MEXC, and Ondo**.

## Open orders

[`GET /exchange/open-orders`](/docs/api-reference/exchange/get-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`](/docs/api-reference/exchange/get-a-single-order-by-its-exchange-order-id-or-your-client-order-id) — 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`](/docs/api-reference/exchange/get-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`](/docs/api-reference/exchange/get-open-orders) or [order history](/docs/trading-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`](/docs/api-reference/exchange-bybit/accept-a-market-s-trading-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

* Orderbook snapshots and diffs stream from [`GET /exchange/updates-orderbook`](/docs/api-reference/exchange/subscribe-to-order-book-updates-via-sse) — see [Real-time updates](/docs/trading-streams#order-book-stream).
* Cross-venue quotes and slippage for a given size / side live in [Quotes and slippage](/docs/trading-quotes).

## Next steps

<CardGroup cols={2}>
  <Card title="History" icon="clock-rotate-left" href="/docs/trading-history">
    Paginated order and trade history
  </Card>

  <Card title="Positions" icon="chart-line" href="/docs/trading-positions">
    Positions, balances, and leverage
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.