nautechsystems/nautilus_trader · error

Failed to fetch product '{product_id}': {e}

Error message

Failed to fetch product '{product_id}': {e}

What it means

Raised by request_instrument when get_product(product_id) fails for the given product. The HTTP client error is wrapped with the offending product_id, so the caller knows which instrument lookup failed before any parsing occurs.

Source

Thrown at crates/adapters/coinbase/src/http/client.rs:1072

        self.cache_instruments(&instruments);
        self.record_product_aliases(&response.products);
        Ok(instruments)
    }

    /// Requests a single instrument by product ID.
    ///
    /// Caches the result on success.
    ///
    /// # Errors
    ///
    /// Returns an error when the HTTP request fails, deserialization fails,
    /// or the product cannot be parsed into a supported instrument.
    pub async fn request_instrument(&self, product_id: &str) -> anyhow::Result<InstrumentAny> {
        let json = self
            .inner
            .get_product(product_id)
            .await
            .map_err(|e| anyhow::anyhow!("Failed to fetch product '{product_id}': {e}"))?;
        let product: crate::http::models::Product =
            serde_json::from_value(json).map_err(|e| anyhow::anyhow!(e))?;
        let ts_init = self.ts_now();
        let instrument = parse_instrument(&product, ts_init)?;
        self.cache_instrument(&instrument);
        self.record_product_aliases(std::slice::from_ref(&product));
        Ok(instrument)
    }

    /// Requests the raw product payload for a product ID.
    ///
    /// Returns the full [`crate::http::models::Product`] so callers can read
    /// derivatives-specific fields (`future_product_details.index_price`,
    /// `funding_rate`, `funding_time`) that are stripped when parsing to a
    /// Nautilus instrument.
    ///
    /// # Errors
    ///

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Verify the product_id exists on Coinbase Advanced Trade (use request_instruments to list valid ids).
  2. Check the wrapped error for 404 (invalid/delisted product) vs 5xx (retry with backoff).
  3. Retry transient failures with exponential backoff.
  4. Pre-populate the instrument cache via request_instruments so get_or_fetch_instrument does not hit this path.

Example fix

// before
let inst = client.request_instrument("BTC-USD").await?;
// after (validate id first)
let valid = client.request_instruments(None).await?;
anyhow::ensure!(valid.iter().any(|i| i.id().as_str() == "BTC-USD"), "unknown product");
let inst = client.request_instrument("BTC-USD").await?;
Defensive patterns

Strategy: validation

Validate before calling

let known: Vec<InstrumentAny> = client.request_instruments(None).await?;
anyhow::ensure!(
    known.iter().any(|i| i.id().as_str() == product_id),
    "product {product_id} not listed on Coinbase"
);

Try / catch

match client.request_instrument(product_id).await {
    Ok(i) => i,
    Err(e) if e.to_string().contains("404") => bail!("delisted/unknown product {product_id}"),
    Err(e) => { backoff_retry(|| client.request_instrument(product_id)).await? }
}

Prevention

When it happens

Trigger: Calling request_instrument(product_id) where the REST GET /products/{id} call fails: unknown product id (404), network error, rate limit, or auth problem.

Common situations: Typo'd or delisted product id (e.g. 'BTC-USD' vs 'BTC-USDC'), instruments that no longer exist on Coinbase, temporary network/API issues during bootstrap, callers like get_or_fetch_instrument resolving cache misses.

Understand the failure class

Background: 'Something went wrong' / 'Request failed (500)' / 'HTTP error! status: 404' — what failed HTTP requests actually mean and how to find the real cause — this error's family across 28 libraries.

Related errors


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