nautechsystems/nautilus_trader · critical · anyhow::Error

API credentials not configured

Error message

API credentials not configured

What it means

In the data-client factory, has_api_credentials() said a credential pair was available (key from config or AX_API_KEY, secret from config or AX_API_SECRET), but Credential::resolve then returned None - meaning a complete pair could not actually be assembled from the provided values plus environment. The classic cause is a half-configured pair: one component supplied in config or env and the other missing or empty, in a combination where the fallback rules cannot complete both sides.

Source

Thrown at crates/adapters/architect_ax/src/factories.rs:105

        _cache: CacheView,
        _clock: Rc<RefCell<dyn Clock>>,
    ) -> anyhow::Result<Box<dyn DataClient>> {
        let ax_config = config
            .as_any()
            .downcast_ref::<AxDataClientConfig>()
            .ok_or_else(|| {
                anyhow::anyhow!(
                    "Invalid config type for AxDataClientFactory. Expected AxDataClientConfig, was {config:?}",
                )
            })?
            .clone();

        let client_id = ClientId::from(name);

        let http_client = if ax_config.has_api_credentials() {
            let credential =
                Credential::resolve(ax_config.api_key.clone(), ax_config.api_secret.clone())
                    .ok_or_else(|| anyhow::anyhow!("API credentials not configured"))?;

            AxHttpClient::with_credentials(
                credential.api_key().to_string(),
                credential.api_secret().to_string(),
                Some(ax_config.http_base_url()),
                None, // orders_base_url
                ax_config.http_timeout_secs,
                ax_config.max_retries,
                ax_config.retry_delay_initial_ms,
                ax_config.retry_delay_max_ms,
                ax_config.proxy_url.clone(),
            )
            .map_err(|e| anyhow::anyhow!("Failed to create HTTP client: {e}"))?
        } else {
            AxHttpClient::new(
                Some(ax_config.http_base_url()),
                None, // orders_base_url
                ax_config.http_timeout_secs,

View on GitHub (pinned to a4b06ed870)

Solutions

  1. Provide BOTH api_key and api_secret in the AxDataClientConfig, or export BOTH AX_API_KEY and AX_API_SECRET
  2. Print env::var("AX_API_KEY").is_ok() and env::var("AX_API_SECRET").is_ok() right before node build to confirm the pair
  3. Watch for empty strings: an exported-but-empty var is not a usable credential
  4. Keep credentials in both places identical (config overrides env) to eliminate mixed sources

Example fix

# before
export AX_API_KEY=...
# AX_API_SECRET never set -> "API credentials not configured"
# after
export AX_API_KEY=...
export AX_API_SECRET=...
# or in config
let cfg = AxDataClientConfig::builder()
    .api_key(Some(key))
    .api_secret(Some(secret))
    .build()?;
Defensive patterns

Strategy: validation

Validate before calling

// Assert a complete pair exists before building the node
let (k, s) = (
    std::env::var("AX_API_KEY").ok().or(config.api_key.clone()),
    std::env::var("AX_API_SECRET").ok().or(config.api_secret.clone()),
);
ensure!(k.as_deref().map_or(false, |v| !v.is_empty()), "AX_API_KEY missing/empty");
ensure!(s.as_deref().map_or(false, |v| !v.is_empty()), "AX_API_SECRET missing/empty");

Prevention

When it happens

Trigger: Setting api_key in config but neither api_secret in config nor AX_API_SECRET in the environment (or vice versa); exporting only one of AX_API_KEY/AX_API_SECRET; empty-string values that count as 'set' for one check but fail resolution; env vars removed between the two checks.

Common situations: Docker/systemd units where only one secret env var was added; CI pipelines masking one var; .env files with a typo'd variable name (AX_API_SECREY).

Related errors


AI-assisted analysis of nautechsystems/nautilus_trader@a4b06ed870 (2026-08-16). Data as JSON: /api/errors/9f6a73b1474ddd03. Report an issue: GitHub.