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 an order detail request

What it means

request_order_detail requires exactly one order identifier: the match on (client_order_id, venue_order_id) accepts (Some, None) or (None, Some); any other combination — both None or both Some — bails. OKX's order-detail endpoint takes either clOrdId or ordId, not both, and the adapter maps exactly one.

Source

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

    async fn request_order_status_report_by_identifier(
        &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 = GetOrderParamsBuilder::default();
        params_builder.inst_id(instrument_id.symbol.inner().to_string());

        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 an order detail request"
            ),
        }

        let params = params_builder
            .build()
            .map_err(|e| anyhow::anyhow!("Failed to build order detail params: {e}"))?;
        let orders = match self.inner.get_order(params).await {
            Ok(orders) => orders,
            Err(e) if e.is_order_not_found() => return Ok(None),
            Err(e) => return Err(e.into()),
        };
        let order = match orders.as_slice() {
            [] => return Ok(None),
            [order] => order,
            _ => anyhow::bail!(
                "Order detail returned {} records for one identifier",
                orders.len(),

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Pass exactly one of client_order_id or venue_order_id — prefer venue_order_id (ordId) if known
  2. Clear one identifier before the call when both are present
  3. Ensure upstream identifier resolution succeeds before invoking the detail request

Example fix

// before
client.request_order_detail(instrument_id, Some(coid), Some(oid)).await?;
// after
client.request_order_detail(instrument_id, None, Some(oid)).await?;
Defensive patterns

Strategy: validation

Validate before calling

match (client_order_id, venue_order_id) {
    (Some(_), None) | (None, Some(_)) => {}
    _ => return Err(anyhow::anyhow!("pass exactly one order identifier")),
}

Type guard

fn single_identifier(c: Option<ClientOrderId>, v: Option<VenueOrderId>) -> bool {
    matches!((c.is_some(), v.is_some()), (true, false) | (false, true))
}

Try / catch

match client.request_order_detail(instrument_id, coid, void).await {
    Ok(Some(report)) => { /* use */ }
    Ok(None) => { /* order not found */ }
    Err(e) => log::error!("order detail failed: {e}"),
}

Prevention

When it happens

Trigger: Calling the OKX HTTP client's order-detail method with both identifiers None, or both Some (ambiguous request).

Common situations: Report generation code that opportunistically fills both IDs (venue rejects the call); fallback logic that neither ID resolved; refactored callers passing both 'to be safe'.

Related errors


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