{"record":{"id":"37f8fe9cfed76287","repo":"BerriAI/litellm","slug":"invalid-model-max-budget-e-example-of-valid-mo-37f8fe","errorCode":null,"errorMessage":"Invalid model_max_budget: {e}. Example of valid model_max_budget: https://docs.litellm.ai/docs/proxy/users","messagePattern":"Invalid model_max_budget: (.+?)\\. Example of valid model_max_budget: https://docs\\.litellm\\.ai/docs/proxy/users","errorType":"validation","errorClass":"ValueError","httpStatus":400,"severity":"error","filePath":"litellm/proxy/management_endpoints/key_management_endpoints.py","lineNumber":6709,"sourceCode":"            return\n        if model_max_budget is not None:\n            from litellm.proxy.proxy_server import CommonProxyErrors, premium_user\n\n            if premium_user is not True:\n                raise ValueError(\n                    f\"You must have an enterprise license to set model_max_budget. {CommonProxyErrors.not_premium_user.value}\"\n                )\n            for _model, _budget_info in model_max_budget.items():\n                assert isinstance(_model, str)\n\n                # Normalize to dict (Pydantic may already parse nested values as BudgetConfig)\n                _info = _budget_info.model_dump() if hasattr(_budget_info, \"model_dump\") else dict(_budget_info)\n                # /CRUD endpoints can pass budget_limit as a string, so we need to convert it to a float\n                if \"budget_limit\" in _info:\n                    _info[\"budget_limit\"] = float(_info[\"budget_limit\"])\n                BudgetConfig(**_info)\n    except Exception as e:\n        raise ValueError(\n            f\"Invalid model_max_budget: {e}. Example of valid model_max_budget: https://docs.litellm.ai/docs/proxy/users\"\n        )\n","sourceCodeStart":6691,"sourceCodeEnd":6712,"githubUrl":"https://github.com/BerriAI/litellm/blob/77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8/litellm/proxy/management_endpoints/key_management_endpoints.py#L6691-L6712","documentation":"Structural validation of model_max_budget: it must be a mapping of model-name (str) -> BudgetConfig-compatible dict, where nested budget_limit must be numeric or a numeric string ('10' is coerced, 'ten' is not). Any exception during iteration or BudgetConfig(**info) construction — non-str model keys, missing/invalid fields, unparseable budget_limit — is re-raised as this ValueError with the original error appended and a docs link. It also wraps the enterprise-license error, so read the embedded text to tell the two apart.","triggerScenarios":"'model_max_budget': 'gpt-4o' (a plain string instead of a dict); {'gpt-4o': {'budget_limit': 'unlimited'}}; values passed as Pydantic BudgetConfig objects that fail a required field; keys like {('gpt','4o'): {...}} (tuple model name fails the isinstance str assert).","commonSituations":"Config YAML where indentation makes model_max_budget a list or scalar; JSON numbers arriving as strings from form data; partial copy-paste of the enterprise example missing budget_duration.","solutions":["Match the documented shape: {'<model-name>': {'budget_limit': <number>, 'budget_duration': '1d'|'1mo'|..., 'budget_id': <optional str>}}","Ensure model names are plain strings and budget_limit is a number or numeric string","If the embedded text says 'enterprise license', fix licensing per that error instead of the data shape","Validate locally with the same rules before sending (see validationCode)"],"exampleFix":"# before\nawait client.post('/key/generate', json={\n    'model_max_budget': {'gpt-4o': {'budget_limit': 'ten dollars'}}   # ValueError: Invalid model_max_budget...\n})\n# after\nawait client.post('/key/generate', json={\n    'model_max_budget': {'gpt-4o': {'budget_limit': 10.0, 'budget_duration': '1d', 'budget_id': 'mb-1'}}\n})","handlingStrategy":"validation","validationCode":"def validate_model_max_budget(mmb: dict) -> None:\n    if mmb in (None, {}):\n        return\n    if not isinstance(mmb, dict):\n        raise TypeError('model_max_budget must be a dict of model -> budget config')\n    for model, info in mmb.items():\n        if not isinstance(model, str):\n            raise TypeError(f'model key must be str, got {type(model).__name__}')\n        limit = info.get('budget_limit') if isinstance(info, dict) else None\n        if limit is None or isinstance(limit, bool):\n            raise ValueError(f'{model}: budget_limit required')\n        float(limit)  # must be numeric or numeric string","typeGuard":"def is_valid_model_max_budget(mmb: object) -> bool:\n    if mmb is None or mmb == {}:\n        return True\n    if not isinstance(mmb, dict):\n        return False\n    for model, info in mmb.items():\n        if not isinstance(model, str) or not isinstance(info, dict):\n            return False\n        try:\n            float(info.get('budget_limit'))\n        except (TypeError, ValueError):\n            return False\n    return True","tryCatchPattern":"try:\n    await client.post('/key/generate', json=payload)\nexcept httpx.HTTPStatusError as e:\n    body = e.response.text\n    if 'enterprise license' in body:\n        raise RuntimeError('license missing: set LITELLM_LICENSE') from e\n    if 'Invalid model_max_budget' in body:\n        raise ValueError(f'bad model_max_budget shape: {payload.get(\"model_max_budget\")}') from e\n    raise","preventionTips":["Construct model_max_budget via a typed dataclass/Pydantic model mirroring BudgetConfig","Coerce budget_limit to float at the client; never pass free text","Read the embedded cause — the same outer message wraps both license and shape failures"],"tags":["budgets","validation","schema","litellm-proxy","keys"],"backgroundTag":"schema-validation-failed","analyzedSha":"77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8","analyzedAt":"2026-08-18T11:44:31.656Z","schemaVersion":2},"datasetVersion":"2026-08-21T13:17:26.733Z"}