BerriAI/litellm · error · WatsonXAIError
Error: Watsonx API key not set. Set WATSONX_API_KEY in envir
Error message
Error: Watsonx API key not set. Set WATSONX_API_KEY in environment variables or pass in as parameter - 'api_key='.
What it means
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.
Source
Thrown at litellm/llms/watsonx/common_utils.py:325
wx_credentials: Final = optional_params.pop(
"wx_credentials",
optional_params.pop("watsonx_credentials", None), # follow {provider}_credentials, same as vertex ai
)
token: str | None = None
if wx_credentials is not None:
api_base = wx_credentials.get("url", api_base)
api_key = wx_credentials.get("apikey", wx_credentials.get("api_key", api_key))
token = wx_credentials.get(
"token",
wx_credentials.get(
"watsonx_token", None
), # follow format of {provider}_token, same as azure - e.g. 'azure_ad_token=..'
)
if api_key is None or not isinstance(api_key, str):
raise WatsonXAIError(
status_code=401,
message="Error: Watsonx API key not set. Set WATSONX_API_KEY in environment variables or pass in as parameter - 'api_key='.",
)
if api_base is None or not isinstance(api_base, str):
raise WatsonXAIError(
status_code=401,
message="Error: Watsonx API base not set. Set WATSONX_API_BASE in environment variables or pass in as parameter - 'api_base='.",
)
return WatsonXCredentials(api_key=api_key, api_base=api_base, token=cast(str | None, token))
def _prepare_payload(self, model: str, api_params: WatsonXAPIParams) -> dict:
payload: Final[dict] = {}
if model.startswith("deployment/"):
return {} # Deployment models do not support 'space_id' or 'project_id' in their payload
payload["model_id"] = model
if api_params["project_id"] is not None:
payload["project_id"] = api_params["project_id"]
else:View on GitHub (pinned to 77b7c6c40c)
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.
Example fix
# before
wx_credentials = {"url": "https://us-south.ml.cloud.ibm.com", "key": "abc123"} # wrong key name
# ... -> WatsonXAIError: Watsonx API key not set...
# after
wx_credentials = {
"url": "https://us-south.ml.cloud.ibm.com",
"apikey": os.environ["WATSONX_APIKEY"], # expected key name
} Defensive patterns
Strategy: validation
Validate before calling
def watsonx_credentials_ok(api_key: str | None, wx_credentials: dict | None) -> bool:
if isinstance(api_key, str) and api_key:
return True
if isinstance(wx_credentials, dict):
k = wx_credentials.get("apikey") or wx_credentials.get("api_key")
if isinstance(k, str) and k:
return True
return False
if not watsonx_credentials_ok(api_key, wx_credentials):
raise RuntimeError("WatsonX needs api_key or wx_credentials['apikey']") Type guard
const watsonxCredsOk = (apiKey?: string, wx?: Record<string, unknown>): boolean => (typeof apiKey === "string" && apiKey.length > 0) || (typeof wx?.apikey === "string" && (wx.apikey as string).length > 0) || (typeof wx?.api_key === "string" && (wx.api_key as string).length > 0);
Try / catch
from litellm.llms.watsonx.common_utils import WatsonXAIError
try:
resp = litellm.completion(model="watsonx/...", messages=msgs)
except WatsonXAIError as e:
if "Watsonx API key not set" in e.message:
raise RuntimeError("Pass api_key or wx_credentials={'apikey': ...}") from e
raise Prevention
- 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.
When it happens
Trigger: 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).
Common situations: 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.
Related errors
- API key is required
- API key is required for OpenAI image variations
- API key is required for Topaz image variations
- CohereException - {original_exception.message}
- api_key is required for Volcengine authentication
AI-assisted analysis of BerriAI/litellm@77b7c6c40c (2026-08-18).
Data as JSON: /api/errors/73b6ab7cfcadfb9e.
Report an issue: GitHub.