BerriAI/litellm · warning · HTTPException
Margin for {provider} must be a number (percentage) or dict
Error message
Margin for {provider} must be a number (percentage) or dict with 'percentage' and/or 'fixed_amount' What it means
Validation error (HTTP 400) from PATCH /config/cost_margin_config: a provider's margin value is neither a number (simple percentage format) nor a dict (complex format with 'percentage'/'fixed_amount') — e.g. a string, list, or null. This is the fallback branch after the isinstance checks for (int, float) and dict both fail.
Source
Thrown at litellm/proxy/management_endpoints/cost_tracking_settings.py:405
if "fixed_amount" in margin_value:
fixed_amount = margin_value["fixed_amount"]
if not isinstance(fixed_amount, (int, float)):
raise HTTPException(
status_code=400,
detail=f"Fixed margin amount for {provider} must be a number",
)
if fixed_amount < 0:
raise HTTPException(
status_code=400,
detail=f"Fixed margin amount for {provider} must be non-negative",
)
if not margin_value: # Empty dict
raise HTTPException(
status_code=400,
detail=f"Margin config for {provider} cannot be empty. Must include 'percentage' and/or 'fixed_amount'",
)
else:
raise HTTPException(
status_code=400,
detail=f"Margin for {provider} must be a number (percentage) or dict with 'percentage' and/or 'fixed_amount'",
)
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_margin_config
config["litellm_settings"]["cost_margin_config"] = cost_margin_config
# Save the updated config to DB
await proxy_config.save_config(new_config=config)
View on GitHub (pinned to 77b7c6c40c)
Solutions
- Send either a bare number ({"openai": 0.08}) or a dict ({"openai": {"percentage": 0.08}}).
- Never send null, strings, or arrays as the margin value.
- Run the payload through a JSON schema check before submitting (see type guard).
Example fix
# before
{"openai": "0.08"}
# after
{"openai": 0.08} Defensive patterns
Strategy: type-guard
Validate before calling
def normalize_margin(v):
if isinstance(v, str):
v = float(v)
if isinstance(v, dict):
assert {"percentage", "fixed_amount"} & set(v), "dict needs percentage/fixed_amount"
else:
assert isinstance(v, (int, float)) and not isinstance(v, bool), "must be number or dict"
return v
cost_margin_config = {k: normalize_margin(v) for k, v in cost_margin_config.items()} Type guard
from typing import Union
def is_margin_value(v: object) -> bool:
if isinstance(v, bool) or v is None:
return False
if isinstance(v, (int, float)):
return True
return isinstance(v, dict) and bool({"percentage", "fixed_amount"} & set(v)) Prevention
- Constrain the margin union type (number | {percentage?, fixed_amount?}) in your own schemas.
- Validate with jsonschema/pydantic before sending to the proxy.
When it happens
Trigger: Sending {"openai": "0.08"}, {"openai": null}, {"openai": [0.08]}, or a boolean; stringified values from config pipelines.
Common situations: JSON payloads built from untyped sources where scalars become strings or nulls; booleans sneaking in from feature-flag systems (bool is not int-checked here for the dict path but True would pass the numeric branch as 1 — the string/list cases are the real hits).
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
Related errors
- Discount for {provider} must be a number
- Margin percentage for {provider} must be a number
- Fixed margin amount for {provider} must be a number
- 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/a70f8df695e1fe74.
Report an issue: GitHub.