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
- Verify the product_id exists via the products list (load_all or the REST products endpoint).
- Check the inner error for 404 (bad/delisted id) vs 401 (auth) vs transport failure.
- Use a canonical product id resolved through the provider's alias map if subscribing with an alias.
- 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
- Validate product IDs against the products list before single-product lookups.
- Cache previously loaded instruments to survive transient/delisted lookups.
- Use canonical IDs (via the adapter's alias map), not venue-agnostic symbols.
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
- Failed to fetch products: {e}
- Derive instruments not found: {missing_ids:?}
- Failed to fetch products: {e}
- Failed to fetch product '{product_id}': {e}
- Failed to fetch accounts: {e}
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/222a54b6200fd28d.
Report an issue: GitHub.