BerriAI/litellm · error · WatsonXAIError

Error: Watsonx API base not set. Set WATSONX_API_BASE in env

Error message

Error: Watsonx API base not set. Set WATSONX_API_BASE in environment variables or pass in as parameter - 'api_base='.

What it means

Companion check to the api_key guard in the same credential step: after merging wx_credentials, api_base must be a non-None str. The URL is resolved from the api_base argument or wx_credentials['url']; if still missing, this WatsonXAIError (401) is raised. The env-chain lookup (WATSONX_API_BASE etc.) used elsewhere does not run on this branch, so the value must arrive via argument or the credentials dict.

Source

Thrown at litellm/llms/watsonx/common_utils.py:330

        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:
            payload["space_id"] = api_params["space_id"]
        return payload

View on GitHub (pinned to 77b7c6c40c)

Solutions

  1. Pass api_base explicitly to the call.
  2. Or include "url" in wx_credentials: wx_credentials={'url': 'https://us-south.ml.cloud.ibm.com', 'apikey': ...}.
  3. Also export WATSONX_API_BASE so other watsonx paths resolve it consistently.
  4. Validate the assembled config object before the request: url must be a non-empty string.

Example fix

# before
wx_credentials = {"apikey": os.environ["WATSONX_APIKEY"]}  # no url
# ... -> WatsonXAIError: Watsonx API base not set...

# after
wx_credentials = {
    "url": "https://us-south.ml.cloud.ibm.com",
    "apikey": os.environ["WATSONX_APIKEY"],
}
Defensive patterns

Strategy: validation

Validate before calling

def watsonx_url_ok(api_base: str | None, wx_credentials: dict | None) -> bool:
    if isinstance(api_base, str) and api_base:
        return True
    if isinstance(wx_credentials, dict):
        url = wx_credentials.get("url")
        if isinstance(url, str) and url:
            return True
    return False

if not watsonx_url_ok(api_base, wx_credentials):
    raise RuntimeError("WatsonX needs api_base or wx_credentials['url']")

Type guard

const watsonxUrlOk = (apiBase?: string, wx?: Record<string, unknown>): boolean =>
  (typeof apiBase === "string" && apiBase.length > 0) ||
  (typeof wx?.url === "string" && (wx.url 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 base not set" in e.message:
        raise RuntimeError("Pass api_base or wx_credentials={'url': ...}") from e
    raise

Prevention

When it happens

Trigger: Passing wx_credentials with only 'apikey' and no 'url'; calling with api_base=None and no url in credentials; api_base accidentally set to a non-string (e.g. a URL object or None from a failed os.getenv chain).

Common situations: Config templating that leaves the url field blank in non-prod; splitting credentials across multiple dicts and dropping the url one; assuming WATSONX_API_BASE env is read here when only argument/credentials are checked.

Related errors


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