A client order ID is an identifier that your application assigns when it places an order. Polyester
reserves the value for that account while the order is open and for a period after it ends. Reusing
a reserved value is rejected as CONFLICT_DUPLICATE_CLIENT_ORDER_ID; Polyester does not replay the
earlier create result. After creation, some order APIs also accept it as a client-defined reference to locate the target order.
It is not a general idempotency key for cancels or modifications.
The field is named clientOrderId in REST, ConnectRPC ProtoJSON, and Polyester SDKs. The protobuf
source field is client_order_id; generated language-specific names vary.
Client order ID versus order ID
| Identifier | Assigned by | Purpose |
|---|---|---|
clientOrderId | Your client | Duplicate guard, client-side correlation, and locating the order later |
orderId | Polyester | Canonical order identity returned after the order is accepted |
Store both values after a successful response. Use orderId as the canonical identity in your
application, while retaining clientOrderId for reconciliation and retry handling.
Format
A client order ID:
- is optional when placing an order
- must contain no more than 36 characters
- may contain ASCII letters, numbers,
.,_,:,/, and- - is case-sensitive
- is scoped to the account or Subaccount that places the order
A UUID is a suitable default because its standard string representation is exactly 36 characters:
0190f3f4-7c2a-7d86-9e2b-7af0f8f8f102Do not put secrets or sensitive user information in client order IDs. They may appear in responses, logs, order history, and support workflows.
Retry behavior
A client order ID is unique among the open orders of an account. After the order ends, the ID stays reserved for at least 60 seconds.
When Polyester receives an order with a client order ID that is reserved for the same account, it
rejects the request with CONFLICT_DUPLICATE_CLIENT_ORDER_ID (ConnectRPC AlreadyExists, REST 409). This applies whether the payload is identical or different, and whether the original order
is open or has ended. A create that was rejected, for example for insufficient funds, can also keep
its ID reserved. CreateOrder (POST /v1/orders) does not return the previously recorded
outcome.
A duplicate conflict proves only that the account-scoped ID is reserved. It does not prove that the payload matched, that the earlier attempt was accepted, or what state an accepted order is in. Use the order read APIs to reconcile the original attempt.
Reconcile a failed or uncertain create
Generate and persist the client order ID before sending the request:
const clientOrderId = crypto.randomUUID();
const order = {
symbol: "BTC-USDC",
side: "buy" as const,
orderType: "limit" as const,
timeInForce: "gtc" as const,
price: "64250.5",
qty: "0.25",
clientOrderId,
};
await client.orders.create(order);When Polyester sent a create but could not confirm it in time, it answers ERROR_CODE_ORDER_CREATE_OUTCOME_UNKNOWN (ConnectRPC DeadlineExceeded, REST 504 with code ORDER_CREATE_OUTCOME_UNKNOWN). The order may have been accepted. For a batch create, every item's
outcome is unknown: read the items back, or repeat the batch with the same request ID and payload
within 15 minutes, which returns the original result of each decided item and admits no item twice.
If the create fails, its outcome is unknown, or its response is lost because of a timeout or network
failure, reconcile by clientOrderId before submitting again:
- Keep the original
clientOrderIdand payload. - Look up the order by
clientOrderId. Over ConnectRPC, callOrdersReadService.GetOrderwithclientOrderId. Over REST,GET /v1/spot/orders/{order_id}takes only an order ID, so list open orders (GET /v1/orders/open) or order history (GET /v1/orders/history) and match theclientOrderIdfield. Retry the read after backoff if the accepted order is not readable yet. - If a repeated create is still necessary, send the same ID and unchanged payload. It can create
the order only when the ID is not reserved. If it returns
CONFLICT_DUPLICATE_CLIENT_ORDER_ID, continue reconciliation because the conflict does not replay the original outcome. - Use a new client order ID only for a deliberate new order after resolving the earlier intent.
This pattern is unsafe:
await retry(() =>
client.orders.create({
...order,
clientOrderId: crypto.randomUUID(),
})
);Generating a new ID for each attempt makes every attempt a new logical order and can place a second order when the first response was merely lost.
When to retry
Retry only when the failure may be temporary, such as:
- a connection failure
- a request timeout
- rate limiting
- temporary service unavailability
Use bounded exponential backoff with jitter. Keep the client order ID and all order parameters unchanged while resolving the original intent. A repeated single-order create is not a replay: it may succeed if the earlier attempt never reserved the ID, or return a duplicate conflict if it did.
Do not retry validation, authorization, insufficient-funds, or conflicting-client-ID errors as if they were temporary. Reconcile a duplicate conflict. A corrected order is a new logical order and needs a new client order ID.
Retention and reuse
Polyester reserves client order IDs according to the order lifecycle:
| Order state | Client order ID behavior |
|---|---|
| Pending or open | Reserved while the order remains active |
| Ended | Reserved for at least 60 seconds after the order is filled, canceled, rejected, or expired |
| After 60 seconds | May remain reserved for longer; there is no fixed release time |
Polyester does not provide an API that reports when a particular ID has been released.
Uniqueness is enforced within the selected account. The same value can be reused across different Subaccounts without conflict, but globally unique values simplify reconciliation and incident investigation.
Using the ID after creation
Order APIs can use a client order ID to locate an existing order. Depending on the endpoint, you can
use either the Polyester-assigned orderId or your clientOrderId to:
- cancel an order
- identify the target of a modification
- retrieve order details with ConnectRPC
GetOrder
REST order retrieval accepts only an order ID. To find an order
by client order ID over REST, list open orders or order history and match clientOrderId.
A cancel of an order that has already ended returns ORDER_ALREADY_TERMINAL, including a retry of
the cancel that ended it. Once the reservation of an ended order's clientOrderId has passed, a
cancel by that clientOrderId may return ORDER_UNKNOWN instead. Retry a cancel by orderId when
you have it, or read the order before treating ORDER_UNKNOWN as an error.
In these operations, clientOrderId identifies the target order. It is not necessarily the
idempotency key for the operation itself. For example, order modification uses a separate requestId so a retry of the same modification can be deduplicated.
GetOrder briefly waits server-side when an accepted order is not readable yet in the API region
you called. If that bounded wait ends before the order can be confirmed, the API returns a temporary
unavailable error instead of NOT_FOUND. Retry the same
ordinary lookup with the same orderId or clientOrderId; no consistency token or sticky session
is required.Client trigger IDs
Creating a trigger requires a client trigger ID, named clientTriggerId in REST and ConnectRPC
ProtoJSON and client_trigger_id in protobuf. Unlike a client order ID, it is an idempotency key
for the create. Use a unique client trigger ID for every trigger.
A client trigger ID is unique per account, Pair, and trigger type. It protects the create while the trigger is active and for 60 seconds after the trigger ends, whether it completed, was canceled, or failed. After those 60 seconds the ID is free again: a create that uses it is a new request and creates a new trigger.
A client trigger ID must contain no more than 36 bytes in UTF-8, which is 36 ASCII characters. A
UUID fits exactly. A longer ID is rejected as a validation error (ConnectRPC InvalidArgument,
REST 400) before the trigger is created.
| Retry | Result |
|---|---|
| Same ID and identical trigger after an accepted create | Returns the same trigger, with the same triggerId |
| Same ID, Pair, and trigger type with different parameters | Rejected as CONFLICT_DUPLICATE_CLIENT_TRIGGER_ID |
| Same ID after a create that failed or was rolled back | The ID may already be used; the retry can be rejected as CONFLICT_DUPLICATE_CLIENT_TRIGGER_ID |
| Same ID more than 60 seconds after the earlier trigger with it ended | Creates a new trigger |
The first three rows apply while the earlier trigger is active and for 60 seconds after it ends.
CONFLICT_DUPLICATE_CLIENT_TRIGGER_ID is error code 47 (ConnectRPC AlreadyExists, REST 409).
Do not rely on a retried create to report the outcome of an earlier one: once that trigger has been over for 60 seconds, the retry creates a second trigger. Read the triggers instead.
If a create fails, a retry returns CONFLICT_DUPLICATE_CLIENT_TRIGGER_ID, or you need the outcome
of an earlier create:
- List the account's triggers with
TriggersService.ListTriggers(GET /v1/triggers) and look for the client trigger ID in the results.ListTriggersdoes not filter by client trigger ID, so page through the results, newest first, and compareclientTriggerId. - If the trigger exists, use it. Do not create it again.
- If it does not exist, retry the create with a new client trigger ID.
SDK behavior
The Polyester TypeScript SDK does not generate a client order ID for order creation. If you omit clientOrderId, the create request has no client-order-ID duplicate guard or reconciliation key.
Generate and persist your own client order ID before the first attempt whenever your application may retry, resume after a restart, or reconcile an uncertain outcome.