nautechsystems/nautilus_trader · error

Both `api_key` and `api_secret` must be provided together

Error message

Both `api_key` and `api_secret` must be provided together

What it means

BitmexWebSocketClient::new requires WebSocket credentials to be complete or absent: api_key and api_secret must both be Some (authenticated) or both None (public). Passing exactly one of them is ambiguous and rejected at construction time.

Source

Thrown at crates/adapters/bitmex/src/websocket/client.rs:121

    ///
    /// # Errors
    ///
    /// Returns an error if only one of `api_key` or `api_secret` is provided (both or neither required).
    #[expect(clippy::too_many_arguments)]
    pub fn new(
        url: Option<String>,
        api_key: Option<String>,
        api_secret: Option<String>,
        account_id: Option<AccountId>,
        heartbeat: u64,
        auth_timeout_secs: Option<u64>,
        transport_backend: TransportBackend,
        proxy_url: Option<String>,
    ) -> anyhow::Result<Self> {
        let credential = match (api_key, api_secret) {
            (Some(key), Some(secret)) => Some(Credential::new(key, secret)),
            (None, None) => None,
            _ => anyhow::bail!("Both `api_key` and `api_secret` must be provided together"),
        };

        let account_id = account_id.unwrap_or(AccountId::from("BITMEX-master"));

        let initial_mode = AtomicU8::new(ConnectionMode::Closed.as_u8());
        let connection_mode = Arc::new(ArcSwap::from_pointee(initial_mode));

        // Placeholder channel until connect() creates the real one
        let (cmd_tx, _cmd_rx) = tokio::sync::mpsc::unbounded_channel::<HandlerCommand>();

        Ok(Self {
            url: url.unwrap_or(BITMEX_WS_URL.to_string()),
            credential,
            heartbeat: Some(heartbeat),
            auth_timeout_secs: auth_timeout_secs.unwrap_or(AUTHENTICATION_TIMEOUT_SECS),
            account_id,
            auth_tracker: AuthTracker::new(),
            signal: Arc::new(AtomicBool::new(false)),

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Provide both api_key and api_secret together, or pass None for both to run in public (unauthenticated) mode
  2. Check your config source/env vars so both BITMEX_API_KEY and BITMEX_API_SECRET are set to non-empty values
  3. If unauthenticated market data is all you need, explicitly pass None for both instead of leaving a half-filled config
  4. Fail fast at startup: validate the credential pair in your config loader before constructing the client

Example fix

// before
let client = BitmexWebSocketClient::new(
    Some(config.api_key), // api_secret missing in config
    None,
    account_id,
    transport_backend,
    proxy_url,
)?;
// after
let (key, secret) = match (config.api_key.as_deref(), config.api_secret.as_deref()) {
    (Some(k), Some(s)) if !k.is_empty() && !s.is_empty() => (Some(k.to_string()), Some(s.to_string())),
    (None, None) => (None, None),
    _ => anyhow::bail!("api_key and api_secret must both be set (or both omitted)"),
};
let client = BitmexWebSocketClient::new(key, secret, account_id, transport_backend, proxy_url)?;
Defensive patterns

Strategy: validation

Validate before calling

fn validate_ws_creds(api_key: &Option<String>, api_secret: &Option<String>) -> Result<(), String> {
    match (api_key, api_secret) {
        (Some(k), Some(s)) if !k.is_empty() && !s.is_empty() => Ok(()),
        (None, None) => Ok(()),
        _ => Err("api_key and api_secret must be provided together".into()),
    }
}

Type guard

fn complete_creds(k: &Option<String>, s: &Option<String>) -> Option<(&str, &str)> {
    match (k, s) {
        (Some(k), Some(s)) if !k.is_empty() && !s.is_empty() => Some((k, s)),
        _ => None,
    }
}

Try / catch

let client = BitmexWebSocketClient::new(api_key, api_secret, account_id, backend, proxy)
    .map_err(|e| {
        if e.to_string().contains("api_key") && e.to_string().contains("api_secret") {
            ConfigError::new("credential pair incomplete: provide both or neither")
        } else { e.into() }
    })?;

Prevention

When it happens

Trigger: Constructing BitmexWebSocketClient::new(...) with api_key set but api_secret None, or vice versa — typically a config struct where only one credential was populated.

Common situations: Environment providing BITMEX_API_KEY but not BITMEX_API_SECRET (or a secret stripped by a deployment secret manager); copy-pasted config where the secret field was left empty; wiring the same credential tuple to multiple adapters where one field got dropped.

Related errors


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