BerriAI/litellm · warning · HTTPException
Discount for {provider} must be a number
Error message
Discount for {provider} must be a number What it means
Validation error (HTTP 400) from PATCH /config/cost_discount_config: the value for each provider key must be an int or float. Python's isinstance check rejects strings like "0.1", booleans aside, None, lists, or nested objects.
Source
Thrown at litellm/proxy/management_endpoints/cost_tracking_settings.py:229
# Validate that all providers are valid LiteLLM providers
invalid_providers: Final = []
for provider in cost_discount_config:
if provider not in LlmProvidersSet:
invalid_providers.append(provider)
if invalid_providers:
raise HTTPException(
status_code=400,
detail={
"error": f"Invalid provider(s): {', '.join(invalid_providers)}. Must be valid LiteLLM providers. See https://docs.litellm.ai/docs/providers for the full list."
},
)
# Validate discount values are between 0 and 1
for provider, discount in cost_discount_config.items():
if not isinstance(discount, (int, float)):
raise HTTPException(status_code=400, detail=f"Discount for {provider} must be a number")
if not (0 <= discount <= 1):
raise HTTPException(
status_code=400,
detail=f"Discount for {provider} must be between 0 and 1 (0% to 100%)",
)
try:
# Load existing config
config: Final = await proxy_config.get_config()
# Ensure litellm_settings exists
if "litellm_settings" not in config:
config["litellm_settings"] = {}
# Update cost_discount_config
config["litellm_settings"]["cost_discount_config"] = cost_discount_config
# Save the updated config to DBView on GitHub (pinned to 77b7c6c40c)
Solutions
- Send a bare JSON number: {"openai": 0.10} not {"openai": "0.10"}.
- If generating the payload programmatically, cast with float()/int() before serializing.
- Check for accidental nulls or nested dicts in the payload.
Example fix
# before
{"openai": "0.10"} # 400 Discount for openai must be a number
# after
{"openai": 0.10} Defensive patterns
Strategy: type-guard
Validate before calling
cleaned = {}
for provider, discount in raw_config.items():
if isinstance(discount, str):
discount = float(discount) # coerce stringified numbers, or fail loudly
if not isinstance(discount, (int, float)) or isinstance(discount, bool):
raise TypeError(f"{provider}: discount must be a number, got {type(discount).__name__}")
cleaned[provider] = discount Type guard
def is_numeric_discount(cfg: dict) -> bool:
return all(
isinstance(v, (int, float)) and not isinstance(v, bool)
for v in cfg.values()
) Prevention
- Never quote numeric values in JSON payloads.
- Cast env-var/template-sourced values with float() before serializing.
- Reject bools explicitly — bool is a subclass of int in Python.
When it happens
Trigger: Sending the discount as a quoted string ("discount": "0.10") — very common when values come from environment variables or YAML-as-string; sending null or an object {"discount": 0.1} instead of a bare number.
Common situations: Values round-tripped through env vars or templates that stringify numbers; JSON built by string concatenation; config pipelines that emit strings for all scalars.
Related errors
- Margin percentage for {provider} must be a number
- Fixed margin amount for {provider} must be a number
- Margin for {provider} must be a number (percentage) or dict
- Invalid provider(s): {', '.join(invalid_providers)}. Must be
- Discount for {provider} must be between 0 and 1 (0% to 100%)
AI-assisted analysis of BerriAI/litellm@77b7c6c40c (2026-08-18).
Data as JSON: /api/errors/14fc7be18d5a3b4f.
Report an issue: GitHub.