# HTTP — `POST /v1/exchange`

Place a batch of up to 20 orders, modify Hyperliquid or Lighter resting orders,
cancel up to 20 orders, request a mass cancel, or create deposit/withdrawal requests. The
beta does not smart-route user orders: every order must choose its `venue`.

Separate HTTP trading requests are independent intents. Mosaiq does not
guarantee execution, venue-send, or finality order between two `/v1/exchange`
requests, even from the same user. If a request is not durably accepted by
Mosaiq before a crash, it is not sent to a venue and may be dropped on recovery.

Trading actions authenticate with an API key. Deposits can use either API key
or Para wallet auth. `withdraw` requires Para wallet auth and cannot be
initiated by an API key.

```http
POST /v1/exchange
Authorization: Bearer <your-api-key>
```

## Place Limit Order

Limit order writes currently support `Ioc` and `Alo`.

:::warning
`Gtc` is not supported yet on `place`. Use `Alo` for maker/post-only resting
orders or `Ioc` for immediate-or-cancel. `modify` has no `t` field and preserves
the existing order's time-in-force.
:::

```jsonc
{
  "type": "place",
  "orders": [
    {
      "a": "BTC",
      "b": "buy",
      "p": "65000",
      "s": "1",
      "venue": "lighter",
      "t": "Alo",
      "c": "0x00000000000000000000000000000002"
    }
  ]
}
```

## Deposit Request

Deposits are USDC on Arbitrum from the registered wallet to the treasury
address. The exchange endpoint creates a tracked request; the deposit watcher
credits the account only after it observes a matching on-chain transfer before
the returned expiry time.

```jsonc
{
  "type": "deposit",
  "amount": "100"
}
```

Response:

```jsonc
{
  "status": "ok",
  "data": [
    {
      "status": "accepted",
      "requestId": "...",
      "kind": "deposit",
      "amount": "100",
      "chain": "arbitrum",
      "from": "0x...",
      "to": "0x...",
      "expiresAtMs": 1780003600100
    }
  ],
  "server_time_ms": 1780000000100,
  "server_receive_time_ms": 1780000000088
}
```

## Withdrawal Request

`withdraw` creates a tracked withdrawal request to the registered wallet. It is
not an instant treasury payout. The request requires Para wallet auth:

```http
Authorization: Bearer <para-jwt>
X-Tessera-Wallet: 0x... # required when the JWT has multiple EVM wallets
```

```jsonc
{
  "type": "withdraw",
  "amount": "100"
}
```

Response:

```jsonc
{
  "status": "ok",
  "data": [
    {
      "status": "accepted",
      "requestId": "...",
      "kind": "withdraw",
      "amount": "100",
      "chain": "arbitrum",
      "destination": "0x..."
    }
  ],
  "server_time_ms": 1780000000100,
  "server_receive_time_ms": 1780000000088
}
```

Post-only example:

```jsonc
{
  "type": "place",
  "orders": [
    {
      "a": "BTC",
      "b": "buy",
      "p": "64000",
      "s": "1",
      "venue": "hyperliquid",
      "t": "Alo",
      "c": "0x00000000000000000000000000000005"
    }
  ]
}
```

## Market Order

Market orders are also `type: "place"`. Mosaiq infers market execution from
`slippage` with no `p`/`t`. `Alo` is limit-only and cannot be combined with
`slippage`.

```jsonc
{
  "type": "place",
  "orders": [
    {
      "a": "BTC",
      "b": "buy",
      "s": "1",
      "slippage": "0.01",
      "venue": "hyperliquid",
      "c": "0x00000000000000000000000000000001"
    }
  ]
}
```

## Reduce-Only

Set `reduceOnly` when an order must only shrink the authenticated user's current
position on the selected venue. Mosaiq validates it against local settled
position state and open reduce-only orders before sending anything venue-side.

```jsonc
{
  "type": "place",
  "orders": [
    {
      "a": "BTC",
      "b": "sell",
      "p": "65000",
      "s": "0.5",
      "venue": "hyperliquid",
      "t": "Ioc",
      "reduceOnly": true,
      "c": "0x00000000000000000000000000000005"
    }
  ]
}
```

## Cancel

Cancels use the public `c` and the venue. The asset is resolved from the
original placement. Venue order ids are internal.

```jsonc
{
  "type": "cancel",
  "orders": [
    { "venue": "lighter", "c": "0x00000000000000000000000000000002" }
  ]
}
```

## Modify

`modify` amends the price and total size of an existing Hyperliquid or Lighter
resting limit order by public client order id `c`. The asset and side must match
the original placement. The order type and time-in-force cannot be changed;
Mosaiq reads them from the existing order. Sending `t` is rejected as an
unknown field.

```jsonc
{
  "type": "modify",
  "orders": [
    {
      "venue": "hyperliquid",
      "c": "0x00000000000000000000000000000005",
      "a": "BTC",
      "b": "buy",
      "p": "63000",
      "s": "1"
    }
  ]
}
```

## Mass Cancel

`mass_cancel` cancels all open orders for the authenticated user. Pass `venues`
to restrict it to selected venues; omit `venues` or pass an empty array to
cancel across all venues.

```jsonc
{
  "type": "mass_cancel",
  "venues": ["hyperliquid"]
}
```

## Batch

`orders` can carry up to 20 actions — all places (mixing limit and market,
across venues), all modifies, or all cancels; place, modify, and cancel are not
mixed in one request. They are
processed in array order (an earlier place reserves margin before a later one,
exactly as if sent one by one), and the response `data` answers item by item, by
index.

```jsonc
{
  "type": "place",
  "orders": [
    { "a": "BTC", "b": "buy", "p": "65000", "s": "1", "venue": "lighter", "t": "Alo", "c": "0x00000000000000000000000000000003" },
    { "a": "ETH", "b": "sell", "s": "0.1", "slippage": "0.01", "venue": "hyperliquid", "c": "0x00000000000000000000000000000004" }
  ]
}
```

| Field      | Type     | Description                                               |
| ---------- | -------- | --------------------------------------------------------- |
| `type`     | `string` | `"place"`, `"modify"`, `"cancel"`, `"mass_cancel"`, `"deposit"`, or `"withdraw"`. |
| `a`        | `string` | Mosaiq canonical asset for place/modify orders.          |
| `b`        | `string` | `"buy"` or `"sell"`.                                      |
| `p`        | `string` | Limit price. Required for limit/modify, absent on market order. |
| `s`        | `string` | Size, in base units for place/modify.                     |
| `slippage` | `string` | Market-order slippage fraction, e.g. `"0.01"` for 1%.     |
| `venue`    | `string` | Venue code returned by `clearinghouseState.universe`, such as `"hyperliquid"`, `"hyperliquid:xyz"`, `"hyperliquid:mkts"`, or `"lighter"`. Required. |
| `venues`   | `array`  | Optional venue filter for `mass_cancel`; empty means all. |
| `t`        | `string` | `"Ioc"` or `"Alo"` for limit placements only. It is not accepted on modify. |
| `reduceOnly` | `boolean` | Optional on place; rejects unless the order reduces the current venue position. |
| `c`        | `string` | Client order id. Optional on place, required on modify/cancel. |

`a` always comes from
[`clearinghouseState.universe`](/api/clearinghouse-state), not from venue
symbols.

`c` is Mosaiq's public client order id. V0 format is lowercase non-zero
hex 128-bit: `0x` plus 32 hex characters. If omitted on placement, Mosaiq
generates one and returns it. A user cannot have two open orders with the
same `c`.

For the authenticated low-latency write transport, see
[Write WebSocket](/api/exchange-websocket).

## Tick Size and Precision

Mosaiq has its own public order standard: clients send canonical `a`, a
selected `venue`, and decimal strings for `p` and `s`. Clients should not encode
Hyperliquid, Lighter, or any other venue-native price format.

There is no single global tick size. Each active venue market exposes its
constraints in
[`clearinghouseState.universe[*].venues[*].constraints`](/api/clearinghouse-state):

| Field           | Meaning                                                   |
| --------------- | --------------------------------------------------------- |
| `tickSize`      | Price grid used for UI prevalidation on this venue.       |
| `priceDecimals` | Maximum decimal places for price on this venue.           |
| `sizeIncrement` | Smallest base-size step accepted on this venue.           |
| `sizeDecimals`  | Maximum decimal places for size on this venue.            |
| `minNotional`   | Minimum quote notional for this venue when available.     |
| `minBaseAmount` | Minimum base size for this venue when available.          |

When venues differ, the selected `venue` wins. Mosaiq then fits the request
when it is economically safe:

* `s` rounds down to the selected venue's size precision and increment.
* buy limit `p` rounds down, so it never becomes more aggressive.
* sell limit `p` rounds up, so it never becomes more aggressive.
* venue-specific format rules are handled by Mosaiq at validation time.
* if fitting makes size zero, falls below minimum notional, or cannot satisfy the
  selected venue safely, the request is rejected.

For live-safe integrations:

1. Fetch [`clearinghouseState`](/api/clearinghouse-state).
2. Choose an asset and a `venues[*]` entry with `status: "active"`.
3. Use `venues[*].constraints.minNotional`, `minBaseAmount`, and
   `sizeIncrement` to choose a size with buffer.
4. Place with a fresh `c`, keep the returned `cloid`, and reconcile with
   [`/v1/openOrders`](/api/open-orders) if the WebSocket window is missed.

`constraints` are useful for UI prevalidation and sizing. Mosaiq remains the
authority for final precision and venue-format validation at order acceptance.

## Response

All REST endpoints return the same top-level `{ status, data, server_time_ms }`
envelope. For `/v1/exchange`, `data` is an array of per-item results, aligned by
index with the request's `orders`. Writes also carry `server_receive_time_ms`.
Order lifecycle, fills, and account impact come later from streams or snapshots.
This index alignment and intra-batch ordering do not imply any ordering guarantee
between separate HTTP requests.

```jsonc
// success
{
  "status": "ok",
  "data": [
    {
      "status": "accepted",
      "orderId": "...",
      "cloid": "0x00000000000000000000000000000002",
      "venue": "hyperliquid"
    }
  ],
  "server_time_ms": 1780000000100,
  "server_receive_time_ms": 1780000000088
}

// whole-request error (auth, rate limit, empty or oversized batch, malformed body)
{
  "status": "error",
  "data": { "code": "...", "msg": "..." },
  "server_time_ms": 1780000000100
}
```

Each item in `data` is tagged by public API `status`:

* `accepted`: the request was accepted. Order placements include `orderId`,
  `cloid`, and `venue`; funding requests include their request metadata. This
  is not order lifecycle finality.
* `cancelled`: the cancel completed successfully (`cloid`, `venue`; a cancel is
  identified by its cloid, so no `orderId`).
* `error`: the item was not applied. For this API action, treat the result as a
  definitive unsuccessful outcome: no client-visible order or cancel state changed
  for that item. The response includes `code`, `msg`, and the `cloid` when known;
  the other items in the batch are unaffected.

The top-level `status` is `ok` as soon as the request is processed (auth, rate
limit, batch size), even if every item is an `error` — per-item failures live in
`data[].status`, not the HTTP status. `code` tells clients how to display the
failure or decide on a follow-up. If a code ends in `_uncertain` or `_timeout`,
the REST action still failed to produce a usable result; reconcile through
`orderUpdates` or `openOrders` before sending another action for the same `cloid`.

Common error codes:

| Code                         | Meaning                                                     |
| ---------------------------- | ----------------------------------------------------------- |
| `unauthorized`               | The bearer token is missing or invalid.                     |
| `invalid_json`               | The request body is not valid JSON.                         |
| `not_found`                  | The requested route does not exist.                         |
| `method_not_allowed`         | The HTTP method is not supported for this route.            |
| `bad_request`                | The request is malformed or fails generic validation.       |
| `unsupported`                | The route exists but this request shape is not supported.   |
| `unsupported_order_type`     | The order type is not supported.                            |
| `unknown_asset`              | `a` is not in Mosaiq's canonical universe.                 |
| `unknown_venue`              | `venue` is not configured.                                  |
| `symbol_mapping_missing`     | Asset and venue exist, but no active mapping is registered. |
| `inactive_market`            | The selected venue marks the market inactive.               |
| `unsupported_tif`            | `t` is not `Ioc` or `Alo`, or is currently unsupported like `Gtc`. |
| `invalid_slippage`           | Market `slippage` is missing, zero/negative, too high, or present on a limit order. |
| `duplicate_cloid`            | The user has an open placement with this `c`, or `c` repeats in the batch (first wins). |
| `cap_exceeded`               | The order would exceed the user's beta cap or margin.       |
| `margin_unavailable`         | Mosaiq cannot evaluate margin for the selected market.     |
| `reduce_only`                | `reduceOnly` was set, but the order would not reduce the current venue position. |
| `place_failed`               | The venue rejected a placement after Mosaiq accepted the request. |
| `place_timeout`              | Mosaiq did not receive venue placement feedback inside the REST wait window. |
| `cancel_failed`              | The venue rejected a cancel after Mosaiq accepted the request. |
| `cancel_timeout`             | Mosaiq did not receive venue cancel feedback inside the REST wait window. |
| `order_not_found_for_cancel` | Cancel by `c` did not match a known user order.             |
| `invalid_precision`          | Mosaiq could not fit price/size to venue constraints.      |
| `invalid_size`               | Size is invalid or rounds to zero.                          |
| `invalid_price`              | Price is invalid or unsafe after venue fitting.             |
| `min_notional`               | Size or notional is below the selected-venue minimum.       |
| `batch_too_large`            | More than 20 actions in one request's `orders`.             |
| `invalid_cloid`              | `c` is not lowercase non-zero hex 128-bit.                  |
| `invalid_cancel`             | Cancel payload is malformed.                                |
| `rate_limited`               | The global HTTP or authenticated-user exchange request limit was exceeded. |

Read `clearinghouseState.universe[*].venues[*].constraints` before placing an
order if you want UI prevalidation. Mosaiq still performs final validation at
acceptance time.

## Fees

Fees are documented separately: see [Fees](/concepts/fees).

## Venue Selection

User orders are sent only to the `venue` you choose. Mosaiq validates that the
venue is active and supports the market; it never falls back to another venue or
to an identity symbol mapping. See [Cross Margin](/concepts/portfolio-margin)
for how fills and open orders affect margin.
