nautechsystems/nautilus_trader · error · anyhow::Error

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

Error message

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

What it means

CoinbaseInstrumentProvider::load fetches a single product by ID via get_product(product_id). Any client-side failure — unknown product returning 404, invalid ID, auth errors, network faults — is wrapped with the product ID for context. The error message includes the requested product_id so you can see exactly which lookup failed.

Source

Thrown at crates/adapters/coinbase/src/provider.rs:113

            .client
            .get_products()
            .await
            .map_err(|e| anyhow::anyhow!("Failed to fetch products: {e}"))?;

        self.load_from_products_response_filtered(&json, product_type)
    }

    /// Loads a single instrument by product ID from the REST API.
    ///
    /// # Errors
    ///
    /// Returns an error if the HTTP request fails or the response cannot be parsed.
    pub async fn load(&self, product_id: &str) -> anyhow::Result<InstrumentAny> {
        let json = self
            .client
            .get_product(product_id)
            .await
            .map_err(|e| anyhow::anyhow!("Failed to fetch product '{product_id}': {e}"))?;

        self.load_from_product_response(&json)
    }

    /// Parses a products list response and caches the instruments.
    ///
    /// Expects the JSON shape returned by `GET /products`: `{"products": [...]}`.
    ///
    /// # Errors
    ///
    /// Returns an error if the JSON cannot be deserialized or any product fails to parse.
    pub fn load_from_products_response(
        &self,
        json: &serde_json::Value,
    ) -> anyhow::Result<Vec<InstrumentAny>> {
        let response: ProductsResponse =
            serde_json::from_value(json.clone()).map_err(|e| anyhow::anyhow!("{e}"))?;

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Verify the product_id exists via the products list (load_all or the REST products endpoint).
  2. Check the inner error for 404 (bad/delisted id) vs 401 (auth) vs transport failure.
  3. Use a canonical product id resolved through the provider's alias map if subscribing with an alias.
  4. Retry transient network/5xx failures with backoff.

Example fix

// before
let btc = provider.load("BTC-USD").await?;
// after
let btc = match provider.load("BTC-USD").await {
    Ok(i) => i,
    Err(e) if e.to_string().contains("404") => {
        eprintln!("product not found; available ids: {:?}", list_ids());
        return Err(e);
    }
    Err(e) => return Err(e),
};
Defensive patterns

Strategy: validation

Validate before calling

let known: HashSet<String> = provider.load_all().await?.iter().map(|i| i.id().to_string()).collect();
anyhow::ensure!(known.contains("BTC-USD"), "product BTC-USD not listed on Coinbase");
let btc = provider.load("BTC-USD").await?;

Try / catch

match provider.load(product_id).await {
    Ok(i) => i,
    Err(e) if e.to_string().contains("404") => { warn!("{product_id} not found/delisted"); fallback_to_cached(product_id) }
    Err(e) => return Err(e.into()),
}

Prevention

When it happens

Trigger: Calling load(product_id) with an unknown/typo'd product ID (404), a delisted product, invalid credentials, network failure, or timeout on the single-product endpoint.

Common situations: Hard-coded product IDs using the wrong format (e.g. 'BTC-USD' vs Coinbase's 'BTC-USDC' or a canonical vs alias id); querying a product after it was delisted; missing auth for endpoints that need it.

Understand the failure class

Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.

Related errors


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