BerriAI/litellm · error · ValueError

'username' is required in litellm_params when…

Error message

'username' is required in litellm_params when auth_mode='cp4d'

What it means

Watsonx Orchestrate auth supports two modes. auth_mode='iam' exchanges the apikey with IAM for a bearer token; the default auth_mode='cp4d' authenticates against Cloud Pak for Data (CP4D), which requires a username in addition to the api_key. When cp4d mode is selected and username is absent from litellm_params, the handler raises this ValueError before attempting the /icp4d-api/v1/authorize call.

Solutions

  1. Add username (e.g. the CP4D admin or service user) to the deployment's litellm_params
  2. If the instance authenticates via IAM instead, set auth_mode: iam and keep only api_key
  3. Confirm cp4d_host points at the CP4D cluster reachable from the proxy
  4. Reload/restart the proxy so the corrected litellm_params take effect

Example fix

# before (config.yaml)
litellm_params:
  model: watsonx_orchestrate/my-agent
  cp4d_host: https://cp4d.example.com
  api_key: os.environ/WXO_API_KEY

# after
litellm_params:
  model: watsonx_orchestrate/my-agent
  cp4d_host: https://cp4d.example.com
  api_key: os.environ/WXO_API_KEY
  username: admin
  auth_mode: cp4d
Defensive patterns

Strategy: validation

Validate before calling

def validate_wxo_auth(litellm_params: dict) -> None:
    auth_mode = litellm_params.get("auth_mode", "cp4d")
    if auth_mode == "cp4d" and not litellm_params.get("username"):
        raise ValueError("username required for auth_mode=cp4d; add it or switch auth_mode to iam")

Try / catch

try:
    result = await WatsonxOrchestrateHandler.handle_request(params)
except ValueError as e:
    if "'username' is required" in str(e):
        raise HTTPException(500, "WXO deployment missing username for cp4d auth") from e
    raise

Prevention

When it happens

Trigger: A watsonx_orchestrate deployment with auth_mode: cp4d (or auth_mode unset, since cp4d is the default) and no username in litellm_params; upgrading configs where username was previously implicit; switching an IAM-configured agent to a CP4D-hosted instance without adding the username.

Common situations: Configuring WXO agents for an on-prem Cloud Pak for Data deployment and only supplying api_key; assuming the IAM-style apikey-only setup works for CP4D; typos like user: instead of username:; forgetting that omitting auth_mode silently selects cp4d.

Related errors


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

Appendix: source

Thrown at litellm/a2a_protocol/providers/watsonx_orchestrate/handler.py:98

        if client is None:
            client = WatsonxOrchestrateHandler._http_client(timeout=30.0)

        if auth_mode == "ibm_cloud":
            response = await client.post(
                _IBM_CLOUD_IAM_URL,
                data={
                    "grant_type": "urn:ibm:params:oauth:grant-type:apikey",
                    "apikey": api_key,
                },
                headers={"Content-Type": "application/x-www-form-urlencoded"},
            )
            response.raise_for_status()
            payload = response.json()
            token = str(payload["access_token"])
            ttl_s = int(payload.get("expires_in", 3600))
        else:
            if not username:
                raise ValueError("'username' is required in litellm_params when auth_mode='cp4d'")
            token_url: Final = f"{cp4d_host.rstrip('/')}/icp4d-api/v1/authorize"
            response = await client.post(
                token_url,
                json={"username": username, "api_key": api_key},
                headers={"Content-Type": "application/json"},
            )
            response.raise_for_status()
            payload = response.json()
            token = str(payload["token"])
            expiration: Final = payload.get("expiration")
            if expiration is None:
                ttl_s = 3600
            else:
                ttl_s = WatsonxOrchestrateHandler._cp4d_token_ttl_seconds(expiration)

        expires_at: Final = now + max(ttl_s - _TOKEN_CACHE_TTL_BUFFER_S, 0)
        _token_cache[cache_key] = (token, expires_at)
        for stale_key, (_, stale_expires_at) in list(_token_cache.items()):

View on GitHub (pinned to 77b7c6c40c)