{"record":{"id":"987eb9068627a8de","repo":"BerriAI/litellm","slug":"invalid-budget-duration-budget-duration-use-a","errorCode":null,"errorMessage":"Invalid budget_duration '{budget_duration}'. Use a format like '1h', '24h', '7d', or '30d'.","messagePattern":"Invalid budget_duration '(.+?)'\\. Use a format like '1h', '24h', '7d', or '30d'\\.","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"warning","filePath":"litellm/proxy/management_endpoints/common_utils.py","lineNumber":46,"sourceCode":"    budget reset job.\n\n    A non-positive duration also resolves to a reset time of \"now\", which leaves\n    the row permanently due: the reset job re-reads it every tick and, once\n    enough of them exist, they fill each batch and starve every other tenant's\n    reset.\n    \"\"\"\n    if budget_duration is None:\n        return\n\n    from litellm.litellm_core_utils.duration_parser import duration_in_seconds\n    from litellm.proxy.common_utils.timezone_utils import get_budget_reset_time\n\n    try:\n        if duration_in_seconds(budget_duration) <= 0:\n            raise ValueError(\"budget_duration must be positive\")\n        get_budget_reset_time(budget_duration=budget_duration)\n    except (ValueError, OverflowError):\n        raise HTTPException(\n            status_code=400,\n            detail={\n                \"error\": f\"Invalid budget_duration '{budget_duration}'. Use a format like '1h', '24h', '7d', or '30d'.\"\n            },\n        )\n\n\nfrom litellm._logging import verbose_proxy_logger\nfrom litellm.caching import DualCache\nfrom litellm.proxy._types import (\n    KeyRequestBase,\n    LiteLLM_ManagementEndpoint_MetadataFields,\n    LiteLLM_ManagementEndpoint_MetadataFields_Premium,\n    LiteLLM_OrganizationTable,\n    LiteLLM_ProjectTable,\n    LiteLLM_TeamTable,\n    LiteLLM_UserTable,\n    LitellmUserRoles,","sourceCodeStart":28,"sourceCodeEnd":64,"githubUrl":"https://github.com/BerriAI/litellm/blob/77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8/litellm/proxy/management_endpoints/common_utils.py#L28-L64","documentation":"validate_budget_duration guards the budget_duration field on key/user/team/budget creation: it parses the string with duration_in_seconds and computes the reset time via get_budget_reset_time inside try/except (ValueError, OverflowError), mapping any failure to HTTP 400 with the supported-format hint. It rejects three classes: unparseable strings ('1 month', 'monthly'), non-positive durations ('0h', '-30d' — which would resolve to a permanently-due reset time), and durations whose date arithmetic overflows (e.g. '999999999d').","triggerScenarios":"POST /key/generate or /team/new with budget_duration='30days', '0h', '-1d', '1mo' (unsupported unit), or an absurd magnitude that overflows timedelta/date math; copying durations from cron or Kubernetes notation ('24h0m0s') which the parser does not accept.","commonSituations":"UIs offering free-text duration inputs; migrating configs from systems using ISO-8601 durations (P30D) or human phrases ('1 month'); users assuming months are supported because budgets are monthly.","solutions":["Use a supported plain value: '1h', '24h', '7d', '30d' (number + one time unit, no spaces)","For non-positive or overflow mistakes, pick a real positive window within date-math limits","Validate the format client-side with a regex like ^\\d+[smhdw]$ before sending"],"exampleFix":"# before\ncurl -X POST http://localhost:4000/key/generate -d '{\"budget_duration\": \"30days\"}'\n# 400 Invalid budget_duration '30days'. Use a format like '1h', '24h', '7d', or '30d'.\n\n# after\ncurl -X POST http://localhost:4000/key/generate -d '{\"budget_duration\": \"30d\"}'","handlingStrategy":"validation","validationCode":"import re\n\nDURATION_RE = re.compile(r\"^\\d+[smhdw]$\")\n\ndef valid_budget_duration(d: str | None) -> bool:\n    if d is None:\n        return True\n    if not DURATION_RE.match(d):\n        return False\n    n = int(d[:-1])\n    return n > 0 and n * 10_000 < 10**9  # stay far below date-math overflow\n\nassert valid_budget_duration(payload.get(\"budget_duration\")), \"use '1h', '24h', '7d', or '30d'\"","typeGuard":"from typing import TypeGuard\nimport re\n\n_DURATION = re.compile(r\"^([1-9]\\d{0,6})[smhdw]$\")\n\ndef is_valid_budget_duration(value: object) -> TypeGuard[str]:\n    \"\"\"Narrows to a positive, non-overflowing duration string the proxy accepts.\"\"\"\n    if not isinstance(value, str):\n        return False\n    m = _duration.match(value)\n    return bool(m) and int(m.group(1)) > 0","tryCatchPattern":"import httpx\n\ntry:\n    r = httpx.post(f\"{PROXY_URL}/key/generate\", json=payload, headers=hdrs)\n    r.raise_for_status()\nexcept httpx.HTTPStatusError as e:\n    if e.response.status_code == 400 and \"Invalid budget_duration\" in e.response.text:\n        payload[\"budget_duration\"] = \"30d\"  # safe default; or surface to the user\n        r = httpx.post(f\"{PROXY_URL}/key/generate\", json=payload, headers=hdrs)\n        r.raise_for_status()\n    else:\n        raise","preventionTips":["Offer a dropdown of durations in UIs instead of free text","Reject '0h'/'-1d' and month-style strings ('1mo', '1 month') client-side","Remember no default is applied: omit budget_duration entirely when unsure"],"tags":["litellm-proxy","validation","duration","format","budget-management"],"backgroundTag":"invalid-duration-format","analyzedSha":"77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8","analyzedAt":"2026-08-18T11:44:31.656Z","schemaVersion":2},"datasetVersion":"2026-08-21T13:17:26.733Z"}