nautechsystems/nautilus_trader · error
API credentials not configured
Error message
API credentials not configured
What it means
When the AX data config DOES have API credentials configured (`has_api_credentials()` is true), the factory tries to resolve them into a `Credential` via `Credential::resolve`. If resolution returns None — the provided key/secret are empty or cannot be combined into a usable credential — the factory raises "API credentials not configured". It protects against silently building an authenticated client with blank credentials.
Solutions
- Set both AX_API_KEY and AX_API_SECRET (or the config's `api_key`/`api_secret` fields) to non-empty values.
- If you intended an unauthenticated client, remove the credential fields entirely instead of passing empty strings so `has_api_credentials()` returns false.
- Log/inspect the config (without secrets) to confirm both fields are populated before calling create.
- Check for secret-management layers (env templating, vault injection) that may substitute empty values at deploy time.
Example fix
// before
let config = AxDataClientConfig { api_key: Some("".into()), api_secret: Some(secret.into()), .. };
// after
let config = AxDataClientConfig {
api_key: Some(api_key), // non-empty, e.g. from env
api_secret: Some(secret),
..
}; Defensive patterns
Strategy: validation
Validate before calling
// before create
let key = ax_config.api_key.as_ref().map(|k| k.into_inner()).unwrap_or_default();
let secret = ax_config.api_secret.as_ref().map(|s| s.into_inner()).unwrap_or_default();
if !key.is_empty() || !secret.is_empty() {
assert!(!key.is_empty() && !secret.is_empty(), "AX credentials must be both set or both unset");
} Try / catch
match factory.create(name, config, ...) {
Err(e) if e.to_string() == "API credentials not configured" => {
eprintln!("check AX_API_KEY / AX_API_SECRET: set both, or neither for unauthenticated use");
Err(e)
}
other => other,
} Prevention
- Validate at startup that both key and secret are non-empty when auth is required.
- Never set credential fields to empty strings; omit them for unauthenticated use.
- Verify secret-injection (env templating/vault) actually populated the values.
- Rotate keys without leaving blanks in the config file.
When it happens
Trigger: Calling `AxDataClientFactory.create` with a config where `api_key`/`api_secret` are set to empty strings, or where only one of key/secret is present and `Credential::resolve` cannot form a pair.
Common situations: Environment variables or config file containing empty-string API key/secret; a placeholder value stripped to empty by secret redaction; partial credential setup where only `api_key` was filled in.
Related errors
- Binance Spot market data mode SBE requires Ed25519 API…
- Both `api_key` and `api_secret` must be provided together
- Coinbase credentials not available; set COINBASE_API_KEY…
- Failed to create HyperSync client - check ENVIO_API_TOKEN…
- is required when is provided
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/9f6a73b1474ddd03.
Report an issue: GitHub.
Appendix: source
Thrown at crates/adapters/architect_ax/src/factories.rs:107
) -> 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().map(|value| value.into_inner()),
ax_config.api_secret.clone().map(|value| value.into_inner()),
)
.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
.as_ref()
.map(|url| url.expose_secret().to_owned()),
)
.map_err(|e| anyhow::anyhow!("Failed to create HTTP client: {e}"))?
} else {
AxHttpClient::new(View on GitHub (pinned to 18893faf8b)