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
- Add username (e.g. the CP4D admin or service user) to the deployment's litellm_params
- If the instance authenticates via IAM instead, set auth_mode: iam and keep only api_key
- Confirm cp4d_host points at the CP4D cluster reachable from the proxy
- 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
- Document per auth_mode which keys are mandatory (cp4d: username+api_key; iam: api_key)
- Add a config lint rule for watsonx_orchestrate deployments covering auth fields
- Prefer env-var indirection for username and api_key and check them at startup
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
- 'api_key' is required in litellm_params for WXO agents
- audience must be provided unless…
- 'cp4d_host' is required in litellm_params for WXO agents
- 'instance_id' is required in litellm_params for WXO agents
- Invalid JWT Submitted
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)