# Batch Orders

Use the batch endpoint to submit and cancel orders for one subaccount in a single request.

```http
POST /v1/order/batch
```

Submit **1 to 20 instructions** of type `NEW` and/or `CANCEL` in one request.

The body is `{ "instructions": [...], "failureBehavior": "ContinueOnFailure" }`. The optional `failureBehavior` field defaults to `ContinueOnFailure`. Each instruction has its own `type`, `data`, and **`signature`**; there is no batch-level signature or `data` wrapper.

* `NEW`: use the same `data` and EIP-712 `TradeOrder` signature as `POST /v1/order`. The outer `type` is `NEW`; `data.type` is `LIMIT` or `MARKET`. Stop and linked-order fields are also supported.
* `CANCEL`: sign the EIP-712 `CancelOrder` fields `sender`, `subaccount`, and `nonce`. In `data`, include **exactly one** singular `orderId` (UUID) or `clientOrderId`. The target ID is not part of the signed message. ID arrays and cancel-all instructions are not supported.

For example, cancel an existing order and submit a new limit order:

```json
{
  "instructions": [
    {
      "type": "CANCEL",
      "data": {
        "sender": "0x...",
        "subaccount": "0x...",
        "nonce": "1712019600000000000",
        "clientOrderId": "a35d8b71-62c4-4f09-ae83-917b0c5d2648"
      },
      "signature": "0x..."
    },
    {
      "type": "NEW",
      "data": {
        "sender": "0x...",
        "subaccount": "0x...",
        "nonce": "1712019600000000001",
        "signedAt": 1712019600,
        "type": "LIMIT",
        "onchainId": 1,
        "engineType": 0,
        "side": 0,
        "quantity": "5.5",
        "price": "4200.5",
        "timeInForce": "GTD",
        "postOnly": true,
        "reduceOnly": false,
        "clientOrderId": "c8f7d962-4a31-4e8b-9f65-2d0a6b3e7149"
      },
      "signature": "0x..."
    }
  ]
}
```

Follow [Message Signing](/developer-guides/trading-api/message-signing#limit-order-example) for the domain, typed data, and decimal encoding.

## Batch Constraints

* A batch can contain orders **across multiple products**. All instructions must use the same bytes32 subaccount name and resolve to the **same owner account and subaccount**. Different authorized signers are allowed.
* Nonces must be **unique within each instruction type**. `NEW` and `CANCEL` may share a nonce, but replay protection is shared with the corresponding standalone endpoints.
* `NEW` instructions cannot repeat a `clientOrderId`. `CANCEL` instructions cannot repeat an `orderId` or repeat a `clientOrderId`. A `NEW` followed by a `CANCEL` may reference the same `clientOrderId`. See [Client Order IDs](#client-order-ids).
* Each new order must satisfy the normal [order and product validation rules](/developer-guides/trading-api/order-placement#product-validation). An invalid value fails only that instruction; see [Invalid Instructions](#invalid-instructions).
* An admitted batch consumes **1 account point per instruction**, including failed instructions. Its IP cost is the sum of the configured standalone submit/cancel costs. See [System Limits](/developer-guides/trading-api/system-limits).

## How a Batch Can Fail

A batch fails in one of three ways:

| Where                      | What fails          | Result                                                                                                                                            |
| -------------------------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| API, before execution      | **The whole batch** | An HTTP error instead of `{ "results": [...] }`. No instruction executes. See [Whole-Batch Rejections](#whole-batch-rejections).                  |
| API, per instruction       | **One instruction** | An invalid quantity, price, stopPrice, or clientOrderId. The instruction is never executed. See [Invalid Instructions](#invalid-instructions).    |
| Execution, per instruction | **One instruction** | The order or cancellation is evaluated and fails on its own, for example for insufficient balance. See [Execution Failures](#execution-failures). |

Once the API accepts a batch, each instruction is executed on its own and the batch is never rejected as a whole. What happens to the remaining instructions after a per-instruction failure of either kind depends on [`failureBehavior`](#failure-behavior).

## Whole-Batch Rejections

The requests below are rejected as a whole: **no instruction executes**, and the response is an error instead of `{ "results": [...] }`. `failureBehavior` does not apply.

HTTP `400` unless noted.

| Category      | Rejected when                                                                                                                                                                                                                                                                                                               |
| ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Request       | Body missing or not JSON<br />Body over the size limit (`413`)<br />`instructions` empty or over 20<br />Unknown top-level field or `failureBehavior`                                                                                                                                                                       |
| Instruction   | Missing or unknown `type`, `data`, or `signature`<br />Unknown, missing, or wrongly typed field<br />Malformed hex, nonce, `signedAt`, or UUID<br />`nonce` or `signedAt` outside the time window<br />Unsupported `side`, `engineType`, `timeInForce`, `stopType`, or `groupContingencyType`<br />`onchainId` out of range |
| Order         | `engineType` not `0` (PERP)<br />`close` without `reduceOnly`, or on a limit order<br />`postOnly` without `GTD`<br />`stopType` without `stopPrice`, or `groupContingencyType` without `groupId`<br />Invalid `expiresAt`<br />`CANCEL` without exactly one of `orderId` or `clientOrderId`                                |
| Batch         | Different subaccounts<br />Repeated nonce or `clientOrderId` across `NEW`s<br />Repeated nonce, `orderId`, or `clientOrderId` across `CANCEL`s                                                                                                                                                                              |
| Authorization | Malformed signature<br />Signature not from `sender`, unregistered subaccount, or inactive or expired linked signer (`401`)<br />Unknown `sender` (`404`)                                                                                                                                                                   |
| Product       | Unknown product (`404`)<br />Pending or delisted product                                                                                                                                                                                                                                                                    |
| Limits        | IP or account rate limit exceeded (`429`). See [System Limits](/developer-guides/trading-api/system-limits)                                                                                                                                                                                                                 |
| Availability  | Maintenance (`503`)<br />Internal error (`500`)                                                                                                                                                                                                                                                                             |

HTTP `500` or a timeout does not prove that nothing executed. The batch may have been processed before the error. **Reconcile orders before resubmitting**; use a unique `clientOrderId` for each order.

## Invalid Instructions

An instruction with an invalid **quantity**, **price**, **stopPrice**, or **clientOrderId** value fails on its own. It is **never executed**, and the rest of the batch continues according to [`failureBehavior`](#failure-behavior).

| Field                               | Rejected values                                                                                                                                                                                             | `result`               |
| ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- |
| `quantity`                          | Not a decimal string, more than 9 decimal places, negative, zero (non-close), non-zero with `close: true`, not a multiple of `lotSize`, above `maxQuantity`, below `minQuantity`, or too large to represent | `InvalidQuantity`      |
| `price`                             | Not a decimal string, more than 9 decimal places, not positive, not a multiple of `tickSize`, outside `minPrice`–`maxPrice`, or more significant digits than can be represented exactly                     | `InvalidPrice`         |
| `stopPrice`                         | The same rules as `price`, plus non-zero without `stopType` or zero with `stopType`                                                                                                                         | `InvalidStopPrice`     |
| `clientOrderId` (`NEW` or `CANCEL`) | Empty, longer than 32 characters (except a canonical 36-character UUID), or not alphanumeric. Braced (`{...}`) and `urn:uuid:` forms are rejected                                                           | `InvalidClientOrderId` |

A rejected `NEW` returns `NEW_FAILED` with the code above and a `message`. No order is created, so there is no `id`. A rejected `CANCEL` returns `CANCEL` with `result: "InvalidClientOrderId"`. An invalid `clientOrderId` is not echoed back; match results by position.

```json
{
  "results": [
    {
      "type": "NEW",
      "id": "01950000-0000-7000-8000-000000000001",
      "filled": "0",
      "result": "Ok"
    },
    {
      "type": "NEW_FAILED",
      "result": "InvalidQuantity",
      "message": "quantity must be a multiple of lot size 0.01"
    },
    {
      "type": "NEW",
      "id": "01950000-0000-7000-8000-000000000002",
      "filled": "0",
      "result": "Ok"
    }
  ]
}
```

Only the **value** of these fields is checked per instruction. A field with the wrong JSON type (for example, a number instead of a decimal string) or a missing required field makes the request malformed and rejects the whole batch.

An instruction with a well-formed but invalid value, such as a price off the tick, still needs a valid signature. A bad signature rejects the whole batch.

With `StopOnFailure`, an invalid instruction counts as an immediate failure: instructions after it are not sent and return `PreviousOrderInBatchRejected`. If an earlier instruction has already failed, the invalid instruction also returns `PreviousOrderInBatchRejected`.

## Execution Failures

Instructions that pass validation are executed against current account and market state. A failure here affects only that instruction and is reported in its result:

* `NEW_FAILED`: no order was created. For example `InsufficientBalance`, `RiskLimitExceeded`, `TooManyOpenOrders`, `TooManyPositions`, `TooManyStopOrders`, `NonceAlreadyUsed`, `DuplicateClientOrderId` (the ID was already used by an earlier order, even one that was canceled or rejected), `InvalidExpireTime`, `InvalidGroupContingencyType`, `OcoLatencyFloorMismatch`, `AccountSuspended`, or `ExchangeSuspended`.
* `NEW_REJECTED`: the order was created and then rejected. For example `ImmediateMatchPostOnly`, `UnfilledImmediateOrCancel`, `UnfilledFillOrKill`, `UnfilledMarketOrder`, `MarketOrderReachedMaxSlippage`, `OrderIncreasesPosition`, `InsufficientBalance`, or `RiskLimitExceeded`.
* `CANCEL`: `NotFound`, `AlreadyCanceled`, `AlreadyFilled`, `AlreadyExpired`, or `NonceAlreadyUsed`.

`Unknown` is not expected. It means the instruction failed for an unrecognized reason. Only that instruction is affected.

Taker orders are also subject to [block execution](/trading/perpetual-futures/block-execution), so a later fill or rejection arrives through WebSocket updates rather than in the batch response.

## Client Order IDs

A `clientOrderId` is used up once an order is created with it, and is never released:

| Case                                                                 | Result                                                                                     |
| -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ |
| `NEW` fails [validation](#invalid-instructions)                      | No order is created. The ID can be used again in a later batch.                            |
| `NEW` is created and then rejected (`NEW_REJECTED`)                  | The ID is used up. A `CANCEL` of it returns `AlreadyCanceled`.                             |
| `NEW` reuses the ID of an open or canceled order                     | That instruction fails with `DuplicateClientOrderId`. The rest of the batch is unaffected. |
| `CANCEL` of an ID whose `NEW` failed validation earlier in the batch | `NotFound` with `ContinueOnFailure`; skipped with `StopOnFailure`.                         |
| Two `NEW` instructions in one batch share an ID                      | The whole batch is rejected, even if one of them is otherwise invalid.                     |
| Several instructions carry the same invalid `clientOrderId` value    | Not a duplicate. Each fails with `InvalidClientOrderId`.                                   |

## Failure Behavior

Set `failureBehavior` alongside `instructions` in the request. Choose `ContinueOnFailure` or `StopOnFailure` for both `NEW` and `CANCEL` instructions. Whole-batch validation and instruction signatures are unchanged.

### ContinueOnFailure (default)

Every instruction is attempted, even if an earlier one fails. Check each result separately.

For `CANCEL` followed by `NEW`, the new order is submitted even if cancellation returns `AlreadyFilled` or `NotFound`. This can leave a replacement open after the old order has filled.

### StopOnFailure

An immediate failure skips all remaining instructions. The failed instruction returns its failure or rejection result; skipped instructions return `PreviousOrderInBatchRejected`. Skipped new orders use `NEW_FAILED`, with no order ID or filled quantity. Skipped cancellations use `CANCEL` and echo their target.

Immediate failures include `NEW_FAILED`, `NEW_REJECTED`, any `CANCEL` result other than `Ok`, and [invalid instructions](#invalid-instructions).

Add `"failureBehavior": "StopOnFailure"` to the request. For `CANCEL` followed by `NEW`, an immediate `AlreadyFilled` result skips the replacement:

```json
{
  "results": [
    {
      "type": "CANCEL",
      "clientOrderId": "a35d8b71-62c4-4f09-ae83-917b0c5d2648",
      "result": "AlreadyFilled"
    },
    {
      "type": "NEW_FAILED",
      "clientOrderId": "c8f7d962-4a31-4e8b-9f65-2d0a6b3e7149",
      "result": "PreviousOrderInBatchRejected"
    }
  ]
}
```

Earlier successes are not undone. If cancellation succeeds but the replacement fails, the old order is not restored.

### Pending Cancellations and Block Execution

`StopOnFailure` only stops on immediate failures. It does not wait for orders or cancellations delayed by the latency floor. A pending cancellation returns `Ok`, so later instructions can proceed.

For example:

1. An order is waiting in an execution block.
2. A `StopOnFailure` batch requests cancellation and submits a replacement. The cancellation is pending and returns `Ok`; the replacement is accepted.
3. When the block is released, the old order fills before cancellation takes effect. The replacement stays open.

Later failures do not stop or undo subsequent instructions. Neither mode guarantees atomic cancel/replace or a particular final position. If the old order must be canceled first, confirm cancellation through WebSocket updates before submitting the replacement. See [Block Execution](/trading/perpetual-futures/block-execution).

## Execution and Results

Once admitted, instructions are processed **in array order** according to `failureBehavior`. Neither mode rolls back earlier successful instructions. **A batch is not an atomic transaction.**

For example, a nonce repeated within the batch rejects the whole request, while a nonce already consumed by an earlier request returns `NonceAlreadyUsed` for that instruction only. See [How a Batch Can Fail](#how-a-batch-can-fail).

HTTP `200` returns `{ "results": [...] }`, with one result per instruction in the same order. Inspect each item's `type` and `result`; **HTTP success does not mean every instruction succeeded.**

With the default `ContinueOnFailure`, the request above can return a failed cancellation followed by a successful submission:

```json
{
  "results": [
    {
      "type": "CANCEL",
      "clientOrderId": "a35d8b71-62c4-4f09-ae83-917b0c5d2648",
      "result": "NotFound"
    },
    {
      "type": "NEW",
      "id": "01950000-0000-7000-8000-000000000001",
      "clientOrderId": "c8f7d962-4a31-4e8b-9f65-2d0a6b3e7149",
      "filled": "0",
      "result": "Ok"
    }
  ]
}
```

| Result `type`  | Fields and meaning                                                                                                                                                                                                                                        |
| -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `NEW`          | `id`, optional `clientOrderId`, `filled`, and `result: "Ok"`. Order created.                                                                                                                                                                              |
| `NEW_REJECTED` | `id`, optional `clientOrderId`, `filled`, and a rejection `result`, such as `ImmediateMatchPostOnly`. Order created but rejected.                                                                                                                         |
| `NEW_FAILED`   | Optional `clientOrderId`, a failure `result`, such as `InsufficientBalance`, `NonceAlreadyUsed`, `InvalidQuantity`, or `PreviousOrderInBatchRejected`, and an optional `message`. No order was created; no `id` or `filled` is returned.                  |
| `CANCEL`       | Echoes the target as `id` or `clientOrderId`, plus `result`: `Ok`, `NotFound`, `AlreadyCanceled`, `AlreadyFilled`, `AlreadyExpired`, `NonceAlreadyUsed`, `InvalidClientOrderId`, `PreviousOrderInBatchRejected`, or `Unknown`, and an optional `message`. |

**Block execution also applies to batch orders.** Taker orders wait in the execution block, just as with individual submissions. `filled` is deprecated and always returns `"0"`. Use WebSocket `OrderUpdate` and `OrderFill` messages for subsequent execution results. See [Block Execution](/trading/perpetual-futures/block-execution).

After a timeout, **reconcile orders before resubmitting**; the batch may already have executed partially or fully.

## Replace an Order

To replace an order, cancel the old order and submit a new order, either as separate requests or as `CANCEL` then `NEW` instructions in a batch. With `ContinueOnFailure`, the replacement can proceed even if cancellation fails immediately. With `StopOnFailure`, an immediate cancellation failure skips the replacement, but an accepted pending cancellation does not. Neither mode rolls back a successful cancellation if the replacement fails. If submission must depend on completed cancellation, use separate requests and confirm the final cancellation before submitting. See [Failure Behavior](#failure-behavior).

Use a unique `clientOrderId` to reconcile each request.
