{"record":{"id":"73b6ab7cfcadfb9e","repo":"BerriAI/litellm","slug":"error-watsonx-api-key-not-set-set-watsonx-api-ke","errorCode":null,"errorMessage":"Error: Watsonx API key not set. Set WATSONX_API_KEY in environment variables or pass in as parameter - 'api_key='.","messagePattern":"Error: Watsonx API key not set\\. Set WATSONX_API_KEY in environment variables or pass in as parameter - 'api_key='\\.","errorType":"exception","errorClass":"WatsonXAIError","httpStatus":401,"severity":"error","filePath":"litellm/llms/watsonx/common_utils.py","lineNumber":325,"sourceCode":"\n        wx_credentials: Final = optional_params.pop(\n            \"wx_credentials\",\n            optional_params.pop(\"watsonx_credentials\", None),  # follow {provider}_credentials, same as vertex ai\n        )\n\n        token: str | None = None\n\n        if wx_credentials is not None:\n            api_base = wx_credentials.get(\"url\", api_base)\n            api_key = wx_credentials.get(\"apikey\", wx_credentials.get(\"api_key\", api_key))\n            token = wx_credentials.get(\n                \"token\",\n                wx_credentials.get(\n                    \"watsonx_token\", None\n                ),  # follow format of {provider}_token, same as azure - e.g. 'azure_ad_token=..'\n            )\n        if api_key is None or not isinstance(api_key, str):\n            raise WatsonXAIError(\n                status_code=401,\n                message=\"Error: Watsonx API key not set. Set WATSONX_API_KEY in environment variables or pass in as parameter - 'api_key='.\",\n            )\n        if api_base is None or not isinstance(api_base, str):\n            raise WatsonXAIError(\n                status_code=401,\n                message=\"Error: Watsonx API base not set. Set WATSONX_API_BASE in environment variables or pass in as parameter - 'api_base='.\",\n            )\n        return WatsonXCredentials(api_key=api_key, api_base=api_base, token=cast(str | None, token))\n\n    def _prepare_payload(self, model: str, api_params: WatsonXAPIParams) -> dict:\n        payload: Final[dict] = {}\n        if model.startswith(\"deployment/\"):\n            return {}  # Deployment models do not support 'space_id' or 'project_id' in their payload\n        payload[\"model_id\"] = model\n        if api_params[\"project_id\"] is not None:\n            payload[\"project_id\"] = api_params[\"project_id\"]\n        else:","sourceCodeStart":307,"sourceCodeEnd":343,"githubUrl":"https://github.com/BerriAI/litellm/blob/77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8/litellm/llms/watsonx/common_utils.py#L307-L343","documentation":"In the endpoint implementer's credential step (used for the OpenAI-compatible watsonx path), the api_key is resolved from wx_credentials (dict with 'apikey'/'api_key'/'token'/'watsonx_token') or the incoming api_key. If it ends up None or not a str, this WatsonXAIError (401) is raised. Note this path checks the function argument / credentials dict only - it does not itself read WATSONX_APIKEY env vars, so the key must arrive via params or upstream resolution.","triggerScenarios":"Calling the watsonx OpenAI-compat endpoint implementer with api_key=None and no wx_credentials; wx_credentials dict passed under a wrong key name (e.g. 'key' instead of 'apikey'); passing a non-string key (None from an os.getenv with no default).","commonSituations":"Switching from the main watsonx handler (which reads WX_API_KEY etc.) to the OpenAI-compatible interface without forwarding the key; building wx_credentials dynamically from missing config; proxy deployments where the api_key mapping was dropped.","solutions":["Pass api_key explicitly: litellm.completion(model=\"watsonx/...\", ..., api_key=os.environ['WATSONX_APIKEY']).","If using wx_credentials, use the key name 'apikey' (or 'api_key'): wx_credentials={'url': ..., 'apikey': ...}.","Set WX_API_KEY / WATSONX_APIKEY env so upstream resolution finds a key.","Double-check the value is a str and not None: api_key = os.getenv('WATSONX_APIKEY'); assert api_key."],"exampleFix":"# before\nwx_credentials = {\"url\": \"https://us-south.ml.cloud.ibm.com\", \"key\": \"abc123\"}  # wrong key name\n# ... -> WatsonXAIError: Watsonx API key not set...\n\n# after\nwx_credentials = {\n    \"url\": \"https://us-south.ml.cloud.ibm.com\",\n    \"apikey\": os.environ[\"WATSONX_APIKEY\"],  # expected key name\n}","handlingStrategy":"validation","validationCode":"def watsonx_credentials_ok(api_key: str | None, wx_credentials: dict | None) -> bool:\n    if isinstance(api_key, str) and api_key:\n        return True\n    if isinstance(wx_credentials, dict):\n        k = wx_credentials.get(\"apikey\") or wx_credentials.get(\"api_key\")\n        if isinstance(k, str) and k:\n            return True\n    return False\n\nif not watsonx_credentials_ok(api_key, wx_credentials):\n    raise RuntimeError(\"WatsonX needs api_key or wx_credentials['apikey']\")","typeGuard":"const watsonxCredsOk = (apiKey?: string, wx?: Record<string, unknown>): boolean =>\n  (typeof apiKey === \"string\" && apiKey.length > 0) ||\n  (typeof wx?.apikey === \"string\" && (wx.apikey as string).length > 0) ||\n  (typeof wx?.api_key === \"string\" && (wx.api_key as string).length > 0);","tryCatchPattern":"from litellm.llms.watsonx.common_utils import WatsonXAIError\n\ntry:\n    resp = litellm.completion(model=\"watsonx/...\", messages=msgs)\nexcept WatsonXAIError as e:\n    if \"Watsonx API key not set\" in e.message:\n        raise RuntimeError(\"Pass api_key or wx_credentials={'apikey': ...}\") from e\n    raise","preventionTips":["Use the exact key names in wx_credentials: 'apikey' or 'api_key' (not 'key').","Do not assume this path reads WATSONX_APIKEY env - pass the key explicitly.","Type-check assembled credentials (str, non-empty) before the request."],"tags":["watsonx","api-key","authentication","credentials","litellm"],"backgroundTag":"missing-api-key","analyzedSha":"77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8","analyzedAt":"2026-08-18T11:44:31.656Z","schemaVersion":2},"datasetVersion":"2026-08-21T13:17:26.733Z"}