# Batch orders

Create, replace, or cancel many orders in one request, read each item's result, and retry safely after a partial failure or an unknown outcome.

A batch sends many order commands in one request. Each item is decided on its own, as if it had been sent alone, and the response carries one result per item in request order. Use batches to place a quote ladder, refresh quotes, or pull a set of orders with one round trip.

- **Batch create** · 1 to 20 items · any Pairs\
  Retry window: 15 minutes.
- **Batch replace** · 1 to 50 items · one Pair\
  Retry window: 60 seconds from acceptance.
- **Batch cancel** · 1 to 50 items · any Pairs\
  Retry window: 2 minutes.

## How batches work

Every batch follows the same rules:

- **Items run in order and stand alone.** A rejected item does not stop the next one, and an admitted item is not rolled back when a later one fails.
- **Results follow the request.** The response lists one result per item, in request order, with `acceptedCount` and `rejectedCount`.
- **`requestId` identifies the batch.** It is required, 1 to 64 ASCII letters, numbers, `.`, `_`, `:`, `/`, or `-`, and scoped to the account. Repeating the same `requestId` and the same items within the retry window returns the original results and acts on nothing twice. The same `requestId` with different items is rejected with `CONFLICT_IDEMPOTENCY_KEY_REUSE` and changes nothing.
- **Each item counts against your rate limit**, as the matching single command does. See [Rate limits](https://testnet.polyester.com/docs/developer-docs/shared-concepts/rate-limits#trading).

A request that is malformed as a whole, such as one with no items, more items than its limit (`BATCH_TOO_LARGE`), or a missing `requestId`, is rejected before any item runs.

> **Use a new requestId for every new batch**
>
> After the retry window, a `requestId` is free again. Repeating an old batch then acts again: a batch create can place its orders a second time. Read the items back instead of repeating an old batch.

## Batch create

[`BatchCreateOrders`](https://testnet.polyester.com/docs/api-docs/connect/orders.v1.OrdersService/BatchCreateOrders) ([`POST /v1/orders/batch`](https://testnet.polyester.com/docs/api-docs/rest/POST/v1/orders/batch)) places up to 20 orders, on any mix of Pairs. Each item is a complete order with the same fields and rules as a single create; see [Order types and lifecycle](https://testnet.polyester.com/docs/developer-docs/shared-concepts/order-types-and-lifecycle). An item that fails validation is rejected alone, with its own code, and the other items proceed.

Over REST, an item whose quantity or price has a nonzero digit beyond the Pair's scale is rejected alone with `QTY_STEP_SIZE` or `PRICE_TICK_SIZE` and a violation such as `items[0].baseQty`. Trailing zeros beyond the scale are accepted.

A client order ID that is already reserved, or that appears twice in the same batch, rejects only its item with `CONFLICT_DUPLICATE_CLIENT_ORDER_ID`. When the holder is a known earlier order, the item's error carries its `existingOrderId`. See [Client order IDs](https://testnet.polyester.com/docs/developer-docs/shared-concepts/client-order-ids).

## REST JSON

```json
{
	"requestId": "quote-0193-a",
	"items": [
		{
			"symbol": "BTC-USDT",
			"side": "BUY",
			"baseQty": "0.25",
			"limitGtc": { "price": "64200", "postOnly": true },
			"clientOrderId": "mm-bid-17"
		},
		{
			"symbol": "BTC-USDT",
			"side": "SELL",
			"baseQty": "0.25",
			"limitGtc": { "price": "64300", "postOnly": true },
			"clientOrderId": "mm-ask-17"
		}
	]
}
```

## ConnectRPC ProtoJSON

Call `/orders.v1.OrdersService/BatchCreateOrders`. This example assumes a Pair base quantity scale of `8`, so `0.25` is `25000000`; prices are in units of 1e-9 quote:

```json
{
	"requestId": "quote-0193-a",
	"items": [
		{
			"symbolId": 1,
			"side": "BUY",
			"baseQtyScaled": "25000000",
			"limitGtc": { "priceTicks": "64200000000000", "postOnly": true },
			"clientOrderId": "mm-bid-17"
		},
		{
			"symbolId": 1,
			"side": "SELL",
			"baseQtyScaled": "25000000",
			"limitGtc": { "priceTicks": "64300000000000", "postOnly": true },
			"clientOrderId": "mm-ask-17"
		}
	]
}
```

Each result echoes `clientOrderId` and holds exactly one of:

- `accepted`: the new `orderId`, the resolved base quantity, and any attached-leg trigger IDs
- `rejected`: an `error` with the item's `code`

An accepted item is admitted, not filled. Its orders report their lifecycle like any other order.

## Batch replace

[`BatchReplaceOrders`](https://testnet.polyester.com/docs/api-docs/connect/orders.v1.OrdersService/BatchReplaceOrders) ([`POST /v1/orders/replace-batch`](https://testnet.polyester.com/docs/api-docs/rest/POST/v1/orders/replace-batch)) modifies up to 50 orders of one Pair as a quote refresh. Name the Pair once for the batch (`symbol` in REST, `symbol_id` in ConnectRPC). Each item names a different order by `orderId` or `clientOrderId` and sets at least one of a new price, a new total quantity, or new attached legs, plus an optional `newClientOrderId`.

Every item follows the default modify rule, `AMEND_OR_REPLACE`:

- An item that keeps the price, does not raise the total quantity, and keeps the client order ID **amends** the order in place, with the same `orderId` and its queue priority.
- A new price, a larger total quantity, or a new client order ID **replaces** the order with a successor that has a new `orderId` and reset queue priority.
- A new total quantity at or below the filled quantity ends the order at its filled quantity, with no successor.

To force a replacement, use `ModifyOrder` with `REPLACE_ONLY`. See [Modify an order](https://testnet.polyester.com/docs/developer-docs/shared-concepts/order-types-and-lifecycle#modify-an-order).

An item whose order is on another Pair is rejected alone as `ORDER_UNKNOWN`. Naming the same order twice rejects the whole batch. Over REST, an item `newPrice` with a nonzero digit beyond the tick scale also rejects the whole request, with `PRICE_TICK_SIZE` and the item's violation, such as `items[1].newPrice`.

### Admission receipt

The response is an admission receipt, returned before the market has applied the items. Its full schema is in the API reference. Read it this way:

- `batchRequestId` identifies the batch and is the same for every retry of it. Keep it for `GetBatchReplaceStatus`.
- The batch `status` is `ADMITTED`, `PARTIALLY_ADMITTED`, or `REJECTED`. Each result's `status` is `ADMITTED`, or `REJECTED` with a `code`.
- Match results by `itemIndex`, the zero-based position of the item in your request, and treat a missing ID as none.
- After `actionTaken` `AMENDED`, keep tracking `oldOrderId`. After `REPLACED`, track `replacementOrderId`, which only a replaced item carries.

REST uses the short enum names shown; ConnectRPC uses the full Protobuf names, such as `BATCH_REPLACE_ADMISSION_STATUS_ADMITTED`.

An admitted item can still be refused by the market, for example a replacement that would cross as post-only. The original order then keeps its price and quantity, and no rejected order appears under the replacement's order ID. Watch the [private order updates](https://testnet.polyester.com/docs/developer-docs/connectrpc/private-websocket), which report `REPLACE_REJECTED` or `AMEND_REJECTED`, or read the batch status.

### GetBatchReplaceStatus

[`OrdersReadService.GetBatchReplaceStatus`](https://testnet.polyester.com/docs/api-docs/connect/orders.v1.OrdersReadService/GetBatchReplaceStatus) returns each item's progress for a `batchRequestId`. It is available over ConnectRPC only, so its values use the full Protobuf names, such as `BATCH_REPLACE_PHASE_WORKING`; the prefix is omitted below.

| Item `phase` | Meaning                                                         |
| ------------ | --------------------------------------------------------------- |
| `ADMITTED`   | Admitted and handed to execution                                |
| `WORKING`    | The successor, or the amended order, is active                  |
| `REJECTED`   | Rejected at admission or refused by the market; `code` says why |
| `TERMINAL`   | The successor, or the amended order, has ended                  |

Each item also carries `orderStatus`, the current status of its successor or amended order. A `NOT_FOUND` answer does not prove that the batch was rejected; reconcile the individual orders instead.

## Batch cancel

[`BatchCancelOrders`](https://testnet.polyester.com/docs/api-docs/connect/orders.v1.OrdersService/BatchCancelOrders) ([`POST /v1/orders/cancel-batch`](https://testnet.polyester.com/docs/api-docs/rest/POST/v1/orders/cancel-batch)) cancels up to 50 orders, on any Pairs. Each item names one order by `orderId` or `clientOrderId`. Over ConnectRPC, an item can also carry `symbol_id`; when set, it must match the order's Pair or the item is rejected as `ORDER_UNKNOWN`.

Each result has `status` (`ACCEPTED` or `REJECTED`), the resolved `orderId`, and a `code` for a rejection. `ACCEPTED` means cancellation was requested: the order can still fill until it ends, and the order's own updates report `CANCELED` or `FILLED`. A cancel never applies twice. An item whose order has already ended is rejected with `ORDER_ALREADY_TERMINAL`; an item named by a client order ID more than a minute after its order ended may be rejected with `ORDER_UNKNOWN` instead.

To cancel by filter instead of by ID, use [Cancel all orders](https://testnet.polyester.com/docs/developer-docs/shared-concepts/cancel-all-orders).

## Partial failure and unknown outcomes

A batch can end without a full answer in two ways:

- **An error for the whole request**, such as `UNAVAILABLE`. For a batch create or batch cancel, some items may already be admitted: Polyester answers with an error while any item is still undecided, even when others went through.
- **An unknown outcome.** `ORDER_CREATE_OUTCOME_UNKNOWN`, `ORDER_MODIFY_OUTCOME_UNKNOWN`, or `ORDER_CANCEL_OUTCOME_UNKNOWN` (REST `504`) means every item's outcome is unknown, and some may still apply. The same applies when the connection drops before the answer.

1. Keep the same requestId and items

   Do not change the payload or generate a new `requestId` while you resolve the batch.

2. Repeat the batch within its retry window

   Send the same `requestId` and items again. Decided items return their original results, and only the undecided items run, so no item is admitted twice.

3. Read the orders back if the window has passed

   Past the window, do not repeat the batch. Read open orders, look up each item by `clientOrderId` or `orderId`, or call `GetBatchReplaceStatus` for a batch replace.

`REQUEST_RECEIPTS_EXHAUSTED` means the account already holds its maximum number of recent modify and batch requests for duplicate detection. Retry once earlier ones expire. All codes are in the [error catalogue](https://testnet.polyester.com/docs/developer-docs/error-handling/error-codes).

## Batches on each interface

- **Batch create**\
  REST: `POST /v1/orders/batch`\
  ConnectRPC: `BatchCreateOrders`\
  SBE WebSocket: `BatchCreateOrders` (template 7)
- **Batch replace**\
  REST: `POST /v1/orders/replace-batch`\
  ConnectRPC: `BatchReplaceOrders`\
  SBE WebSocket: `BatchReplaceOrders` (template 8)
- **Batch cancel**\
  REST: `POST /v1/orders/cancel-batch`\
  ConnectRPC: `BatchCancelOrders`\
  SBE WebSocket: `BatchCancelOrders` (template 9)

REST takes Pair symbols and decimal strings, except the new quantity in a batch replace item, which is the scaled integer `newQtyScaled`. A batch replace item whose `newPrice` has a nonzero digit beyond 9 fractional digits fails the whole request with `400` `PRICE_TICK_SIZE` and that item's violation, such as `items[1].newPrice`; trailing zeros are accepted. ConnectRPC takes Pair IDs and scaled integers; see [Scaled integers](https://testnet.polyester.com/docs/developer-docs/connectrpc/scaled-integers). Over the SBE WebSocket, the batch `correlationId` is the idempotency key and the answers are `BatchCreateAck`, `BatchReplaceAck`, and `BatchCancelAck`; see [Batch commands](https://testnet.polyester.com/docs/developer-docs/sbe-websocket/order-entry#batch-commands). Full field lists are in the [API reference](https://testnet.polyester.com/docs/api-docs).
