Service methods are async functions on Client services. Learn the conventions once; they apply
across orders, triggers, balances, and market data.
Call shape
use polyester::models::{CreateOrderParams, CreateOrderType, CreateSide};
use polyester::Quantity;
let quantity_scale = client
.catalogs
.base_quantity_scale_for_symbol("BTC-USDT")
.ok_or_else(|| polyester::Error::validation("BTC-USDT quantity scale is unavailable"))?;
let params = CreateOrderParams {
symbol: "BTC-USDT".into(),
side: CreateSide::Buy,
order_type: CreateOrderType::Limit,
quantity: Some(Quantity::from_decimal_str("0.01", quantity_scale, Some("BTC-USDT".into()), None)?),
max_quote_debit_scaled: None,
price: None,
time_in_force: None,
client_order_id: Some("strategy-run-20260725-0001".into()),
subaccount_id: None,
post_only: None,
market_client_ref_price: None,
fee_asset: None,
self_trade_prevention: None,
market_max_slippage: None,
attached_risk: None,
};
let result = client.orders.create(params).await?;
let _ = result;- Methods return
polyester::Result<T>. - Account scope is often an
Optionon the params/request (or client defaults). - Some reads take proto request structs (for example balances
GetBalancesRequest). - Cancel surrounding work by dropping/aborting the Tokio task, there is no TypeScript-style
AbortSignalbag on every call.
There is no TypeScript stepUpToken options bag on these mutations. Creating API keys is a
JWT/session product flow, not available on this API-key SDK.
Stable mutation identifiers
| Mutation | Key |
|---|---|
orders.create / batch items | client_order_id (duplicate guard) |
orders.modify, cancel_all, batch helpers | request_id (correlation/deduplication) |
triggers.create | client_trigger_id |
internal_transfers.create | idempotency_key (required) |
withdraw.create_to_* | idempotency_key and nonce (required) |
Withdrawal and internal-transfer identities are required for every call, including one-shot attempts. Do not treat them as "only for retries."
client_order_id remains optional, and the SDK does not generate one. If you omit it on create,
the call is accepted but you cannot safely reconcile by client ID. A retained value cannot be
reused: even an identical create returns CONFLICT_DUPLICATE_CLIENT_ORDER_ID instead of replaying
the earlier result. Keep the original value after an ambiguous response, look up the order by that
ID, and handle a duplicate conflict as a signal to continue reconciliation. In contrast, client_trigger_id is required and never generated; create and persist it before the first
trigger attempt and reuse it for reconciliation and retries.
If you omit request_id on modify, cancel_all / cancel_all_with, cancel_all_after, or batch create/cancel/modify, the SDK generates one for that single call
(same idea as TypeScript/Go/Python). Fine for one-shot mutations. To retry safely after an
ambiguous failure, supply and reuse your own value - a blind retry that omits request_id mints
a new id and is not an idempotent replay.
let client_order_id = "strategy-run-20260725-0001".to_owned(); // create and persist once
// put Some(client_order_id) on create params
// On an ambiguous error, reconcile by the SAME client_order_id before deciding to retry.
let request_id = "mod-strategy-run-20260725-0001".to_owned(); // choose once per logical modify
// params.request_id = Some(request_id);
// On mutation_outcome_unknown, reconcile then retry modify with the SAME request_id.
let client_trigger_id = "stop-strategy-run-20260725-0001".to_owned(); // required; persist once
// params.client_trigger_id = client_trigger_id;
// Reconcile and retry the same logical trigger with the SAME client_trigger_id.Client order IDs accept 1 to 36 ASCII letters, digits, ., _, :, /, and -. Request IDs
use the same character set and accept 1 to 64 characters. The Rust SDK validates both locally and
returns Error::Validation before the request is sent.
client_order_id while the first create is unresolved can place a
second order. Persist the original value for reconciliation. For replayable request IDs and trigger
identities, create the key once outside the loop and reuse it for the same logical action. The SDK
generates neither client ID; request-ID generation is only for the single call.request_id does not make a multi-item batch atomic. After an ambiguous batch timeout, the same request_id may complete only the remaining work, replay a cached result, or reject source items
that already changed. Reconcile every item before and after retrying.
Retrying safely
Use err.is_retryable() to identify failures that may succeed after backoff, and honor err.retry_after() when present. This is not proof that a mutation was unapplied. When err.mutation_outcome_unknown() is true, reconcile server state before retrying. A repeated
single-order create with a retained client_order_id returns a duplicate conflict rather than the
original result. For mutations with replayable request IDs, reuse the original key, nonce, and
request identifier. For batch mutations, reconcile every item because partial commit is possible.
Do not blindly retry Error::Validation or most Error::Api responses; fix input, authentication,
or state.
Error::ResponseContract is not retryable even though mutation_outcome_unknown() is true. It
means the RPC succeeded but its response violated the SDK contract. Reconcile the mutation using
the original stable identifier and escalate or inspect server state instead of blindly resending.
See Error handling and Errors.
Pagination cursors
Most list APIs return opaque tokens (next_page_token) or similar cursors. Replay them exactly as
returned, including trigger list/event pages.