All fallible SDK APIs use polyester::Result<T> = Result<T, Error>.
For usage patterns see the Error handling guide.
Use err.is_retryable() for backoff classification, err.retry_after() for a server-requested
delay, and err.mutation_outcome_unknown() to identify mutations that require reconciliation
before retrying. These helpers never prove that a mutation was unapplied.
Variants
| Variant | When |
|---|---|
Error::Auth | Unary Unauthenticated / PermissionDenied |
Error::PermissionDenied { message, status, code, context, endpoint } | Realtime token HTTP 403 with structured context |
Error::Validation | Bad input (post_only, ids, enums, shapes, scale) |
Error::Transport | Network / deadline, usually retryable with backoff |
Error::ResponseContract { context, message } | Successful RPC returned an invalid response shape |
Error::RateLimit { message, retry_after } | Server rate limit or local signing-capacity limit |
Error::Server | Backend 5xx-class |
Error::Api { message, code, metadata } | Structured Connect/API error |
Error::RouteNotFound { procedure } | RPC not exposed |
Error::Realtime | Realtime connect / decode |
Error::QueueOverflow | Subscription queue full (fail-closed) |
use polyester::models::{CreateOrderParams, CreateOrderType, CreateSide};
use polyester::{Error, Quantity};
let quantity_scale = client
.catalogs
.base_quantity_scale_for_symbol("BTC-USDT")
.ok_or_else(|| 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("mm-bot-001".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,
};
match client.orders.create(params).await {
Ok(r) => println!("{}", r.order_id),
Err(err) if err.mutation_outcome_unknown() => {
// reconcile by client_order_id before deciding whether to resubmit
return Err(err);
}
Err(err) if err.is_retryable() => {
let _ = err.retry_after(); // sleep when present; reconcile this create
}
Err(Error::Validation(msg)) => eprintln!("fix input: {msg}"),
Err(Error::QueueOverflow(_)) => { /* resubscribe, not a unary retry */ }
Err(err) => return Err(err),
}There is no separate catalog-conversion error type like TypeScript. Catalog / precision failures
typically surface as Error::Validation or Error::Api.
Error::ResponseContract is deliberately not retryable: the server returned success, but the
SDK could not safely interpret the response. For an order mutation, mutation_outcome_unknown() is still true because the mutation may have been accepted. Reconcile
by the original stable identifier; do not turn this error into a blind retry loop.
Unary Connect order APIs map both Unauthenticated and PermissionDenied to Error::Auth. Error::PermissionDenied is not their unary mapping; it carries structured context specifically
for an HTTP 403 while acquiring a private realtime token.
MFA helpers
On Error: auth_error_code(), is_mfa_enrollment_required(), is_step_up_required(), is_mfa_elevation_required(), is_mfa_last_factor_required(), for shared auth codes.
API-key create-key / wallet MFA UX is not part of this SDK (JWT/session only).
Retry sketch
Use is_retryable() for backoff. When mutation_outcome_unknown() is true, reconcile first and
reuse the same client_order_id / request_id / client_trigger_id. Queue overflow is
fail-closed for that subscription.
High-level async calls apply signing backpressure with Tokio-aware waits; they do not block an
executor worker. Direct synchronous Credentials::sign_request returns a retryable rate-limit
error immediately when the bounded timestamp window is full. Async low-level integrations should
use Credentials::sign_request_async.
Class notes
| Concern | Guidance |
|---|---|
| Auth | Fix credentials / Account ID before retrying |
| Validation | Includes post_only on non-limit-GTC |
| Transport / rate limit | Backoff; reconcile when outcome is unknown |
| Response contract | Reconcile; non-retryable without new evidence |
| Queue overflow | Resubscribe; do not treat as unary retry |
| Route not found | Wrong host / incomplete env |