# Order entry

Create, cancel, modify, batch, and cancel-all commands on the SBE WebSocket: required fields, idempotency, and the answer each command produces.

Every order command acts on the account your session is bound to and is answered once, in command order. An acknowledgement means the command was admitted, not that an order is working, filled, or canceled: [order and execution events](https://testnet.polyester.com/docs/developer-docs/sbe-websocket/responses-and-events) report what happens next.

- **`CreateOrder`** · template 2 · answered by `CommandAck` (`CREATE_ORDER`)\
  Retry identity: none; reconcile by `clientOrderId`.
- **`CancelOrder`** · template 3 · answered by `CommandAck` (`CANCEL_ORDER`)\
  Retry identity: none; a cancel never applies twice.
- **`ModifyOrder`** · template 4 · answered by `CommandAck` (`MODIFY_ORDER`)\
  Retry identity: `correlationId`.
- **`CancelAllOrders`** · template 5 · answered by `CommandAck` (`CANCEL_ALL_ORDERS`)\
  Retry identity: `correlationId`, 120 seconds.
- **`BatchCreateOrders`** · template 7 · answered by `BatchCreateAck`\
  Retry identity: `correlationId`, 15 minutes.
- **`BatchReplaceOrders`** · template 8 · answered by `BatchReplaceAck`\
  Retry identity: `correlationId`.
- **`BatchCancelOrders`** · template 9 · answered by `BatchCancelAck`\
  Retry identity: `correlationId`.

Any command can instead be answered with a `CommandReject`. Field layouts are in the [Message reference](https://testnet.polyester.com/docs/developer-docs/sbe-websocket/message-reference).

## Create an order

`CreateOrder` submits one order.

| Field                                              | Rule                                                                                                                                                                                              |
| -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `symbolId`                                         | The Pair's `symbol_id`; required                                                                                                                                                                  |
| `side`                                             | `BUY` or `SELL`                                                                                                                                                                                   |
| `orderType`                                        | `LIMIT` or `MARKET`                                                                                                                                                                               |
| `timeInForce`                                      | `GTC`, `IOC`, `FOK`, or `GTD`. `MARKET` orders require `IOC`. `GTD` requires a `LIMIT` order                                                                                                      |
| `priceTicks`                                       | Required and positive for `LIMIT`; null for `MARKET`                                                                                                                                              |
| `sizingKind` and quantity                          | See [Create-order sizing](#create-order-sizing)                                                                                                                                                   |
| `flags`                                            | `postOnly` (bit 0) rejects an order that would execute immediately; valid for `GTC` and `GTD` limit orders                                                                                        |
| `feeAsset`                                         | `QUOTE` or `BASE`; `BASE` is valid only for `BUY`. See [Fee Assets](https://testnet.polyester.com/docs/developer-docs/shared-concepts/fee-assets)                                                 |
| `stpMode`                                          | `EXPIRE_MAKER`, `EXPIRE_TAKER`, or `EXPIRE_BOTH`; null means `EXPIRE_MAKER`. See [Self-trade prevention](https://testnet.polyester.com/docs/developer-docs/shared-concepts/self-trade-prevention) |
| `marketClientRefPriceTicks`                        | Optional reference price for `MARKET` price protection                                                                                                                                            |
| `marketMaxSlippageTicks` or `marketMaxSlippageBps` | Optional `MARKET` slippage limit; set at most one. See [Market order price protection](https://testnet.polyester.com/docs/developer-docs/shared-concepts/market-order-price-protection)           |
| `expireAtTsNs`                                     | Exact expiry in nanoseconds since the epoch; required and in the future for `GTD`, null otherwise                                                                                                 |
| `clientOrderId`                                    | Optional, 1 to 36 ASCII characters; strongly recommended                                                                                                                                          |

A successful create returns a `CommandAck` with the new public `orderId`, the `symbolId`, and `resolvedBaseQtyScaled`. It also returns `submittedMaxQuoteDebitScaled` when a quote budget was submitted.

### Create-order sizing

Use the `CreateOrder` layout from the [schema for your environment](https://testnet.polyester.com/docs/developer-docs/sbe-websocket/getting-started#schema-version). Set `sizingKind` and exactly one matching quantity field:

| `sizingKind`      | Required value field  | Field that must use its null value |
| ----------------- | --------------------- | ---------------------------------- |
| `BASE_QUANTITY`   | `baseQtyScaled`       | `maxQuoteDebitScaled`              |
| `MAX_QUOTE_DEBIT` | `maxQuoteDebitScaled` | `baseQtyScaled`                    |

`MAX_QUOTE_DEBIT` is valid only for BUY Market IOC and BUY Limit IOC. The order spends the whole quote amount at the prices it pays and never more, then ends canceled at its filled quantity (see [Order sizing](https://testnet.polyester.com/docs/developer-docs/shared-concepts/order-sizing#maximum-quote-debit)). `BASE_QUANTITY` is an exact gross base quantity.

Set `feeAsset` to `QUOTE` or `BASE`. `BASE` is valid only for BUY. A successful create acknowledgement returns `resolvedBaseQtyScaled` and returns `submittedMaxQuoteDebitScaled` when a quote budget was submitted.

> **Populating baseQtyScaled alone is not enough**
>
> `sizingKind` is required. A zero-initialized or omitted sizing enum is rejected even when `baseQtyScaled` contains a positive value.

The SBE command path does not expose order preview. Use [`POST /v1/orders/preview`](https://testnet.polyester.com/docs/api-docs/rest/POST/v1/orders/preview) (ConnectRPC [`PreviewOrder`](https://testnet.polyester.com/docs/api-docs/connect/orders.v1.OrdersService/PreviewOrder)) when an integration needs a non-mutating account admission check; see [Preview order](https://testnet.polyester.com/docs/developer-docs/shared-concepts/preview-order). See [Order sizing](https://testnet.polyester.com/docs/developer-docs/shared-concepts/order-sizing) for quantity fields and acknowledgement semantics.

### Retrying a create

`CreateOrder` has no replay. Resending it, even byte for byte, creates a second order unless the `clientOrderId` is still reserved, in which case it is rejected with `CONFLICT_DUPLICATE_CLIENT_ORDER_ID`. That reject carries `existingOrderId`, the order that holds the ID, when it is known. `correlationId` does not deduplicate creates.

Use the reject code to decide:

- **`CommandAck`** · the order was admitted\
  Track it by `orderId`.
- **`UNAVAILABLE`** · the order was not accepted\
  Retry with the next `seq`; if the connection then closes with `1013`, reconcile first.
- **`ORDER_CREATE_OUTCOME_UNKNOWN`** · the order was sent but not confirmed; it may exist\
  Read the order by `clientOrderId` before deciding to resend; the connection closes with `1013`.
- **No answer** · the connection dropped first\
  Read the order by `clientOrderId` before deciding to resend.

Read an order by its client order ID with [`GetOrder`](https://testnet.polyester.com/docs/api-docs/connect/orders.v1.OrdersReadService/GetOrder), which accepts either an order ID or a client order ID. Without a `clientOrderId`, a lost acknowledgement can leave the outcome unknown. See [Client order IDs](https://testnet.polyester.com/docs/developer-docs/shared-concepts/client-order-ids) for retention and reconciliation.

## Cancel an order

`CancelOrder` names the order by exactly one of `orderId` or `clientOrderId`; set the other to `0` or empty. `symbolId` is optional: `0` lets the service resolve the Pair from the order.

The `CommandAck` returns the `orderId` whose cancellation was requested. The order can still fill until the cancellation completes; its `OrderEvent` reports `CANCELED` or `FILLED`. A cancellation never applies twice. A cancel of an order that has already ended is rejected with `ORDER_ALREADY_TERMINAL`, including a repeat of the cancel that ended it; treat it as already done and read the order for how it ended. A repeat sent while the first cancel is still in progress is acknowledged again and changes nothing.

A `symbolId` that names another Pair than the order's is rejected with `ORDER_UNKNOWN` and cancels nothing; an order that ended a while ago may instead be reported `ORDER_ALREADY_TERMINAL`. A `clientOrderId` keeps naming its order for at least a minute after the order ends, so a later cancel by `clientOrderId` may be rejected with `ORDER_UNKNOWN`. Cancel by `orderId` when you have it. Every cancel, a repeat included, counts against the account's cancel rate limit.

`ORDER_CANCEL_OUTCOME_UNKNOWN` means the cancel was sent but not confirmed and may still apply. Read the order before retrying. `CANCEL_REQUEST_EXPIRED` means the cancel expired before it was admitted; send a new one.

## Modify an order

`ModifyOrder` changes the quantity, price, or client order ID of one open order.

| Field                        | Rule                                                                              |
| ---------------------------- | --------------------------------------------------------------------------------- |
| `orderId` or `clientOrderId` | Exactly one, naming the order to modify                                           |
| `symbolId`                   | Optional; `0` resolves the Pair from the order                                    |
| `newQtyScaled`               | Optional new total gross base quantity, not the remaining quantity; null keeps it |
| `priceTicks`                 | Optional new limit price; null keeps it                                           |
| `newClientOrderId`           | Optional new client order ID; empty keeps it                                      |
| `behavior`                   | Required: `AMEND_OR_REPLACE`, `AMEND_ONLY`, or `REPLACE_ONLY`                     |
| `correlationId`              | Required and nonzero: the modification's identity                                 |

Send at least one change. The behaviors:

- `AMEND_OR_REPLACE` amends the order in place when it can, keeping its `orderId` and queue priority, and otherwise replaces it.
- `AMEND_ONLY` amends in place and rejects a change that needs a replacement with `MODIFICATION_REQUIRES_REPLACE`. A new client order ID always needs a replacement.
- `REPLACE_ONLY` always cancels the order and creates a successor with a new `orderId` and reset queue priority.

The acknowledgement's `orderId` is the order that carries the result: the same ID after an amend, or the successor's ID after a replace. A new total quantity at or below the filled quantity ends the order at its filled quantity and is answered with `QTY_AT_OR_BELOW_FILLED`.

Keep `correlationId` and the request body unchanged when you retry, including after reconnecting. Use a fresh `correlationId` for each new modification. Reusing one with a different body is rejected. `ORDER_MODIFY_IN_PROGRESS` and `ORDER_PLACEMENT_PENDING` mean an earlier change or the order's placement has not completed; retry shortly. `ORDER_MODIFY_OUTCOME_UNKNOWN` means the change may still apply: read the order before retrying.

## Cancel all orders

`CancelAllOrders` cancels every open order of the account that matches its filter:

- `symbols`: up to 100 positive `symbolId` values; an empty group matches every Pair, and duplicates are ignored. More than 100 is rejected with `BAD_REQUEST`.
- `side`: `BUY`, `SELL`, or null (`0`) for both.
- `dryRun`: `1` counts the matching orders without canceling them; `0` cancels.
- `correlationId`: required and nonzero; the cancellation's identity.

The `CommandAck` for a cancel-all reports `matchedOrders`, `submittedCancels`, and `failedCancels`.

The `CommandAck` for a cancel-all that is not a dry run lists the canceled orders in its `cancelledOrders` group: up to 200 `orderId` and `clientOrderId` entries, oldest first. When `cancelledOrdersTruncated` is `1`, more orders were canceled than listed; read the remaining [open orders](https://testnet.polyester.com/docs/api-docs/rest/GET/v1/orders/open) over REST or ConnectRPC. `listingStatus` is `INCLUDED` on the first answer. Resending the same `correlationId` and criteria within 120 seconds returns the original counts with an empty list and `listingStatus` `NOT_RETAINED_READ_OPEN_ORDERS`, and requests no new cancellations. See [Cancel All Orders](https://testnet.polyester.com/docs/developer-docs/shared-concepts/cancel-all-orders).

Use a fresh `correlationId` for each new cancel-all, including after the 120-second window. Each canceled order reports its final state in its own `OrderEvent`. Cancel-all does not cancel standalone triggers.

## Batch commands

Each batch item is admitted or rejected on its own, and each item counts against your rate limit. The batch `correlationId` is its account-scoped idempotency key: resending the same `correlationId` and items answers the original per-item results instead of acting again. A batch create is recognised for 15 minutes.

If a batch is answered with `UNAVAILABLE`, or its acknowledgement never arrives, some items may already be admitted. Resend the same `correlationId` and items to learn each item's result. Do not assume that nothing changed.

The same `correlationId` with different items is rejected item by item with `CONFLICT_IDEMPOTENCY_KEY_REUSE`. The identity is shared with the `request_id` of the REST and ConnectRPC batch operations, such as [`POST /v1/orders/batch`](https://testnet.polyester.com/docs/api-docs/rest/POST/v1/orders/batch), but the payloads never match across encodings, so reusing one identity across interfaces is rejected the same way and changes nothing.

A structurally invalid batch, such as one with no items or more than its limit, is rejected as a whole with a `CommandReject` and does not use its sequence number.

### BatchCreateOrders

Up to 20 orders, on any Pairs. Each item has the same fields and rules as `CreateOrder`. An invalid item is rejected alone; the others proceed. A `clientOrderId` that is reserved, or repeated within the batch, rejects only its item with `CONFLICT_DUPLICATE_CLIENT_ORDER_ID`.

`BatchCreateAck` carries `acceptedCount`, `rejectedCount`, and one result per item in request order: `status` (`ACCEPTED` or `REJECTED`), `code`, `orderId`, `resolvedBaseQtyScaled`, `submittedMaxQuoteDebitScaled`, and `existingOrderId` for a duplicate client order ID.

`ORDER_CREATE_OUTCOME_UNKNOWN` on a batch create means every item's outcome is unknown. Resend the same `correlationId` and items within 15 minutes: decided items return their original results, and the rest are retried without admitting any item twice.

### BatchReplaceOrders

Up to 50 modifications on one Pair, named once in the batch's `symbolId`, each naming a different order. Each item has `orderId` or `clientOrderId`, and at least one of `newQtyScaled` or `priceTicks`, plus an optional `newClientOrderId`.

Every item follows `AMEND_OR_REPLACE`:

- Keeping the price, not raising the total quantity, and keeping 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`. A malformed item, a repeated target, or a missing `symbolId` rejects the whole batch; a missing `symbolId` answers `BATCH_SINGLE_SYMBOL_REQUIRED`. An item whose order is on another Pair is rejected alone as `ORDER_UNKNOWN`; the other items are still processed.

`BatchReplaceAck` carries `status` (`ADMITTED`, `PARTIALLY_ADMITTED`, or `REJECTED`), the counts, `batchRequestId` (the same for every retry of the batch), and one result per item: `status`, `code`, `action` (`AMENDED` or `REPLACED`), `oldOrderId`, and `replacementOrderId`. After `AMENDED`, keep tracking `oldOrderId`; after `REPLACED`, track `replacementOrderId`. To read each item's later progress, pass `batchRequestId` to [`GetBatchReplaceStatus`](https://testnet.polyester.com/docs/api-docs/connect/orders.v1.OrdersReadService/GetBatchReplaceStatus).

The acknowledgement answers at admission, so an admitted item can still be refused when it is applied, for example a post-only order moved to a price that would cross. The modify then changes nothing: the order keeps its previous price and quantity, and the planned `replacementOrderId` never becomes an order, so no rejected order appears under it. Treat that ID as planned until order events or reads confirm it. The same holds for a `ModifyOrder` that replaces its order.

### BatchCancelOrders

Up to 50 cancellations, each naming one order by `orderId` or `clientOrderId`, with an optional `symbolId`. `BatchCancelAck` carries the counts and one result per item: `status`, `code`, and the `orderId`. An accepted item means cancellation was requested; order events report the final state. A cancellation never takes effect twice. A retry answers an item whose order has since ended with `ORDER_ALREADY_TERMINAL`, or with `ORDER_UNKNOWN` when it names the order by a `clientOrderId` that no longer names it.

## Not available on this interface

Triggers (stop, take-profit, trailing, TWAP, ladder), attached take-profit and stop-loss legs, cancel-all-after, order preview, and reads are not available over SBE WebSocket. Use REST or ConnectRPC for them, for example [`POST /v1/triggers`](https://testnet.polyester.com/docs/api-docs/rest/POST/v1/triggers) and [`POST /v1/orders/cancel-all-after`](https://testnet.polyester.com/docs/api-docs/rest/POST/v1/orders/cancel-all-after); their orders still appear in your SBE order and execution events.
