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

  1. Pass api_key explicitly: litellm.completion(model="watsonx/...", ..., api_key=os.environ['WATSONX_APIKEY']).
  2. If using wx_credentials, use the key name 'apikey' (or 'api_key'): wx_credentials={'url': ..., 'apikey': ...}.
  3. Set WX_API_KEY / WATSONX_APIKEY env so upstream resolution finds a key.
  4. 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

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


AI-assisted analysis of BerriAI/litellm@77b7c6c40c (2026-08-18). Data as JSON: /api/errors/73b6ab7cfcadfb9e. Report an issue: GitHub.