{"record":{"id":"3fc3dd81f00ed3d1","repo":"nautechsystems/nautilus_trader","slug":"both-api-key-and-api-secret-must-be-provided-t","errorCode":null,"errorMessage":"Both `api_key` and `api_secret` must be provided together","messagePattern":"Both `api_key` and `api_secret` must be provided together","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/adapters/bitmex/src/websocket/client.rs","lineNumber":121,"sourceCode":"    ///\n    /// # Errors\n    ///\n    /// Returns an error if only one of `api_key` or `api_secret` is provided (both or neither required).\n    #[expect(clippy::too_many_arguments)]\n    pub fn new(\n        url: Option<String>,\n        api_key: Option<String>,\n        api_secret: Option<String>,\n        account_id: Option<AccountId>,\n        heartbeat: u64,\n        auth_timeout_secs: Option<u64>,\n        transport_backend: TransportBackend,\n        proxy_url: Option<String>,\n    ) -> anyhow::Result<Self> {\n        let credential = match (api_key, api_secret) {\n            (Some(key), Some(secret)) => Some(Credential::new(key, secret)),\n            (None, None) => None,\n            _ => anyhow::bail!(\"Both `api_key` and `api_secret` must be provided together\"),\n        };\n\n        let account_id = account_id.unwrap_or(AccountId::from(\"BITMEX-master\"));\n\n        let initial_mode = AtomicU8::new(ConnectionMode::Closed.as_u8());\n        let connection_mode = Arc::new(ArcSwap::from_pointee(initial_mode));\n\n        // Placeholder channel until connect() creates the real one\n        let (cmd_tx, _cmd_rx) = tokio::sync::mpsc::unbounded_channel::<HandlerCommand>();\n\n        Ok(Self {\n            url: url.unwrap_or(BITMEX_WS_URL.to_string()),\n            credential,\n            heartbeat: Some(heartbeat),\n            auth_timeout_secs: auth_timeout_secs.unwrap_or(AUTHENTICATION_TIMEOUT_SECS),\n            account_id,\n            auth_tracker: AuthTracker::new(),\n            signal: Arc::new(AtomicBool::new(false)),","sourceCodeStart":103,"sourceCodeEnd":139,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/adapters/bitmex/src/websocket/client.rs#L103-L139","documentation":"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.","triggerScenarios":"Constructing BitmexWebSocketClient::new(...) with api_key set but api_secret None, or vice versa — typically a config struct where only one credential was populated.","commonSituations":"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.","solutions":["Provide both api_key and api_secret together, or pass None for both to run in public (unauthenticated) mode","Check your config source/env vars so both BITMEX_API_KEY and BITMEX_API_SECRET are set to non-empty values","If unauthenticated market data is all you need, explicitly pass None for both instead of leaving a half-filled config","Fail fast at startup: validate the credential pair in your config loader before constructing the client"],"exampleFix":"// before\nlet client = BitmexWebSocketClient::new(\n    Some(config.api_key), // api_secret missing in config\n    None,\n    account_id,\n    transport_backend,\n    proxy_url,\n)?;\n// after\nlet (key, secret) = match (config.api_key.as_deref(), config.api_secret.as_deref()) {\n    (Some(k), Some(s)) if !k.is_empty() && !s.is_empty() => (Some(k.to_string()), Some(s.to_string())),\n    (None, None) => (None, None),\n    _ => anyhow::bail!(\"api_key and api_secret must both be set (or both omitted)\"),\n};\nlet client = BitmexWebSocketClient::new(key, secret, account_id, transport_backend, proxy_url)?;","handlingStrategy":"validation","validationCode":"fn validate_ws_creds(api_key: &Option<String>, api_secret: &Option<String>) -> Result<(), String> {\n    match (api_key, api_secret) {\n        (Some(k), Some(s)) if !k.is_empty() && !s.is_empty() => Ok(()),\n        (None, None) => Ok(()),\n        _ => Err(\"api_key and api_secret must be provided together\".into()),\n    }\n}","typeGuard":"fn complete_creds(k: &Option<String>, s: &Option<String>) -> Option<(&str, &str)> {\n    match (k, s) {\n        (Some(k), Some(s)) if !k.is_empty() && !s.is_empty() => Some((k, s)),\n        _ => None,\n    }\n}","tryCatchPattern":"let client = BitmexWebSocketClient::new(api_key, api_secret, account_id, backend, proxy)\n    .map_err(|e| {\n        if e.to_string().contains(\"api_key\") && e.to_string().contains(\"api_secret\") {\n            ConfigError::new(\"credential pair incomplete: provide both or neither\")\n        } else { e.into() }\n    })?;","preventionTips":["Load both BITMEX_API_KEY and BITMEX_API_SECRET together in your config struct as a single Credential type","Fail at config-load time if exactly one credential is set","Pass explicit None for both when running public market-data-only clients","Check secret-manager deployments inject both keys, not just the key id"],"tags":["rust","bitmex","websocket","configuration","credentials"],"backgroundTag":"conflicting-config-options","analyzedSha":"18893faf8b356be3320add8de2f861b0b647cf06","analyzedAt":"2026-09-08T20:49:34.690Z","contentChangedAt":"2026-09-08T20:49:34.690Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}