{"record":{"id":"f3bb21230b04e3f3","repo":"BerriAI/litellm","slug":"only-premium-users-can-add-tags-to-keys-commonpr","errorCode":null,"errorMessage":"Only premium users can add tags to keys. {CommonProxyErrors.not_premium_user.value}","messagePattern":"Only premium users can add tags to keys\\. (.+?)","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"litellm/proxy/management_endpoints/key_management_endpoints.py","lineNumber":1067,"sourceCode":"        data_json[\"budget_id\"] = _budget_id\n\n    # Only set budget_duration on key when explicitly provided. Keys with budget_id\n    # but no explicit budget_duration follow their linked budget tier's schedule;\n    # reset_budget_for_litellm_budget_table() resets them when the tier resets.\n    # This avoids duplicating budget_duration on keys so tier updates apply automatically.\n    if \"budget_duration\" in data_json:\n        data_json[\"key_budget_duration\"] = data_json.pop(\"budget_duration\", None)\n\n    if user_api_key_dict.user_id is not None:\n        data_json[\"created_by\"] = user_api_key_dict.user_id\n        data_json[\"updated_by\"] = user_api_key_dict.user_id\n\n    # Set tags on the new key\n    if \"tags\" in data_json:\n        from litellm.proxy.proxy_server import premium_user\n\n        if premium_user is not True and data_json[\"tags\"] is not None:\n            raise ValueError(f\"Only premium users can add tags to keys. {CommonProxyErrors.not_premium_user.value}\")\n\n        _metadata: Final = data_json.get(\"metadata\")\n        if not _metadata:\n            data_json[\"metadata\"] = {\"tags\": data_json[\"tags\"]}\n        else:\n            data_json[\"metadata\"][\"tags\"] = data_json[\"tags\"]\n\n        data_json.pop(\"tags\")\n\n    # Validate MCP servers in object_permission are within team scope\n    _is_proxy_admin_caller: Final = user_api_key_dict.user_role == LitellmUserRoles.PROXY_ADMIN.value\n    normalized_object_permission: Final = await validate_key_mcp_servers_against_team(\n        object_permission=data_json.get(\"object_permission\"),\n        team_obj=team_table,\n        prisma_client=prisma_client,\n        is_proxy_admin=_is_proxy_admin_caller,\n    )\n    if normalized_object_permission is not None:","sourceCodeStart":1049,"sourceCodeEnd":1085,"githubUrl":"https://github.com/BerriAI/litellm/blob/77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8/litellm/proxy/management_endpoints/key_management_endpoints.py#L1049-L1085","documentation":"Key tagging is an enterprise feature: while building the key record, LiteLLM raises a plain ValueError (not an HTTPException) if \"tags\" is present in the payload and the server is not premium (LITELLM_LICENSE unset/invalid). Because it is a ValueError it escapes the normal error mapping and usually surfaces as an HTTP 500 rather than a clean 400.","triggerScenarios":"POST /key/generate with a top-level \"tags\": [\"...\"] field, on a proxy running without a valid LITELLM_LICENSE (premium_user is not True).","commonSituations":"Copying examples that use tags for cost tracking on the open-source proxy; testing locally without the enterprise license then deploying the same payload; upgrading from a trial license that expired.","solutions":["Remove the \"tags\" field (or move it into metadata if you only need arbitrary metadata) for non-premium deployments","Set a valid enterprise license: export LITELLM_LICENSE=<key> on the proxy and restart","Get a trial key from https://www.litellm.ai/enterprise#trial if you want to evaluate the feature"],"exampleFix":"// before\ncurl -X POST http://localhost:4000/key/generate \\\n  -d '{\"tags\": [\"team-a\"]}'\n\n// after: non-premium proxy stores tags as plain metadata\ncurl -X POST http://localhost:4000/key/generate \\\n  -d '{\"metadata\": {\"tags\": [\"team-a\"]}}'","handlingStrategy":"validation","validationCode":"IS_ENTERPRISE = os.getenv(\"LITELLM_LICENSE\") is not None  # mirror server config\nif not IS_ENTERPRISE:\n    payload.pop(\"tags\", None)  # avoid the enterprise-only path entirely\nrequests.post(f\"{PROXY}/key/generate\", headers=AUTH, json=payload)","typeGuard":null,"tryCatchPattern":"try:\n    resp = requests.post(f\"{PROXY}/key/generate\", headers=AUTH, json=payload)\nexcept requests.HTTPError as e:\n    # ValueError on the server surfaces as 500 with this text\n    if e.response.status_code == 500 and \"Only premium users can add tags\" in e.response.text:\n        payload.pop(\"tags\", None)\n        resp = requests.post(f\"{PROXY}/key/generate\", headers=AUTH, json=payload)\n    else:\n        raise","preventionTips":["Gate tags behind a deployment feature flag that mirrors LITELLM_LICENSE","Store unstructured labels in metadata.tags on OSS deployments","Run integration tests against a license-matched environment so premium fields fail in CI, not prod"],"tags":["litellm-proxy","licensing","enterprise","virtual-keys","tags"],"backgroundTag":"enterprise-license-required","analyzedSha":"77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8","analyzedAt":"2026-08-18T11:44:31.656Z","schemaVersion":2},"datasetVersion":"2026-08-21T13:17:26.733Z"}