nautechsystems/nautilus_trader · error

Exactly one of client_order_id or venue_order_id is required

Error message

Exactly one of client_order_id or venue_order_id is required for a spread order detail request

What it means

When requesting a single spread order's details from OKX, exactly one identifier must be supplied: either the client order ID (sent as `clOrdId`) or the venue order ID (sent as `ordId`). Passing both, or neither, is ambiguous/invalid, so the client bails before building the request.

Source

Thrown at crates/adapters/okx/src/http/client.rs:7049

    pub(crate) async fn request_spread_order_status_report(
        &self,
        account_id: AccountId,
        instrument_id: InstrumentId,
        client_order_id: Option<ClientOrderId>,
        venue_order_id: Option<VenueOrderId>,
    ) -> anyhow::Result<Option<OrderStatusReport>> {
        let instrument = self.instrument_from_cache(instrument_id.symbol.inner())?;
        let mut params_builder = GetSpreadOrderParamsBuilder::default();

        match (client_order_id, venue_order_id) {
            (Some(client_order_id), None) => {
                params_builder.cl_ord_id(client_order_id.as_str().to_string());
            }
            (None, Some(venue_order_id)) => {
                params_builder.ord_id(venue_order_id.as_str().to_string());
            }
            _ => anyhow::bail!(
                "Exactly one of client_order_id or venue_order_id is required for a spread order detail request"
            ),
        }

        let params = params_builder
            .build()
            .map_err(|e| anyhow::anyhow!("Failed to build spread order detail params: {e}"))?;
        let orders = match self.inner.get_spread_order(params).await {
            Ok(orders) => orders,
            Err(e) if e.is_order_not_found() => return Ok(None),
            Err(e) => return Err(e.into()),
        };
        let Some(order) = orders.into_iter().next() else {
            return Ok(None);
        };
        let ts_init = self.generate_ts_init();
        let report = parse_spread_order_status_report(
            &order,

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Pass exactly one of `client_order_id` or `venue_order_id` — prefer `venue_order_id` if you have it from a prior report.
  2. If both are available, drop the client order ID and use the venue order ID.
  3. If neither is known, first query order status reports to obtain an order ID.

Example fix

// before
client.request_spread_order_detail(Some(&cl_ord_id), Some(&venue_ord_id)).await?;
// after
client.request_spread_order_detail(None, Some(&venue_ord_id)).await?;
Defensive patterns

Strategy: validation

Validate before calling

let n = client_order_id.is_some() as u8 + venue_order_id.is_some() as u8;
if n != 1 {
    return Err(anyhow::anyhow!("pass exactly one of client_order_id or venue_order_id"));
}

Prevention

When it happens

Trigger: Calling the spread order detail query with `client_order_id` and `venue_order_id` both `Some`, or both `None`.

Common situations: Passing an Nautilus `ClientOrderId` together with a `VenueOrderId` populated by reconciliation; calling the query from generic report code where neither ID was set.

Related errors


AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08). Data as JSON: /api/errors/5715093ab7adde25. Report an issue: GitHub.