BerriAI/litellm · error · HTTPException
Only proxy admins can set {ESTIMATED_OUTPUT_TOKENS_FIELD} or
Error message
Only proxy admins can set {ESTIMATED_OUTPUT_TOKENS_FIELD} or {ESTIMATED_OUTPUT_TOKENS_PER_MODEL_FIELD} on a {entity}. They decide how many output tokens the rate limiter reserves for a request that omits max_tokens. What it means
Raised as HTTP 403 by the estimated-output-tokens guard in litellm/proxy/auth/auth_utils.py when a caller whose role is not PROXY_ADMIN tries to change the rate-limiter reservation fields (estimated_output_tokens / estimated_output_tokens_per_model) on an entity (team, user, or key). Those fields decide how many output tokens the rate limiter pre-reserves for requests that omit max_tokens, so letting a team admin or end user weaken them would undermine limits set above them. The check compares the requested values against the stored ones, so resubmitting the unchanged stored declaration is a no-op and passes.
Source
Thrown at litellm/proxy/auth/auth_utils.py:1162
"""Only a proxy admin may change what a key or team declares its models emit.
That declaration is what the TPM limiter reserves for a request omitting
``max_tokens``, so lowering or clearing it under-reserves against every
window the request is charged against, including the team and organization
ones the writer may not own. A key's metadata is writable by its holder and
a team's by its team admin, so neither is a trustworthy source for a value
that weakens a limit set above them. Gated on the resulting value rather
than on presence, so a form resending the stored declaration stays a no-op.
"""
if user_api_key_dict.user_role == LitellmUserRoles.PROXY_ADMIN.value:
return
stored: Final[Mapping[str, object]] = existing_metadata or {}
if _requested_output_token_estimates(data, stored) == (
stored.get(ESTIMATED_OUTPUT_TOKENS_FIELD),
stored.get(ESTIMATED_OUTPUT_TOKENS_PER_MODEL_FIELD),
):
return
raise HTTPException(
status_code=403,
detail={
"error": f"Only proxy admins can set {ESTIMATED_OUTPUT_TOKENS_FIELD} or "
f"{ESTIMATED_OUTPUT_TOKENS_PER_MODEL_FIELD} on a {entity}. They decide how many output tokens "
"the rate limiter reserves for a request that omits max_tokens."
},
)
def get_model_rate_limit_from_metadata(
user_api_key_dict: UserAPIKeyAuth,
metadata_accessor_key: Literal["team_metadata", "organization_metadata", "project_metadata"],
rate_limit_key: Literal["model_rpm_limit", "model_tpm_limit"],
) -> dict[str, int] | None:
if getattr(user_api_key_dict, metadata_accessor_key):
return getattr(user_api_key_dict, metadata_accessor_key).get(rate_limit_key)
return None
View on GitHub (pinned to 77b7c6c40c)
Solutions
- Have a proxy admin (role proxy_admin) make the change, since only that role may set these fields
- Remove estimated_output_tokens / estimated_output_tokens_per_model from the update payload if the caller is not a proxy admin
- If resubmitting an unchanged form, make sure the values sent exactly match the stored ones - equality with stored metadata is allowed
Example fix
# before: team admin sends changed reservation values
await client.post("/team/update", json={"team_id": tid, "metadata": {"estimated_output_tokens": 999999}})
# after: non-admin omits the admin-only fields
await client.post("/team/update", json={"team_id": tid, "metadata": {"notes": "updated by team admin"}}) Defensive patterns
Strategy: validation
Validate before calling
ADMIN_ONLY_FIELDS = {"estimated_output_tokens", "estimated_output_tokens_per_model"}
def sanitize_metadata_for_role(metadata: dict, user_role: str) -> dict:
if user_role != "proxy_admin":
return {k: v for k, v in metadata.items() if k not in ADMIN_ONLY_FIELDS}
return metadata Type guard
def is_proxy_admin(user_api_key_dict) -> bool:
"""True only for the role allowed to set rate-limiter reservation fields."""
return getattr(user_api_key_dict, "user_role", None) == "proxy_admin" Try / catch
from fastapi import HTTPException
try:
await client.post("/team/update", json=payload)
except HTTPException as e:
if e.status_code == 403 and "estimated_output_tokens" in e.detail.get("error", ""):
escalate_to_proxy_admin(payload) # only admins may set these fields
else:
raise Prevention
- Strip estimated_output_tokens(_per_model) from update payloads in non-admin tooling and UIs
- Keep admin-curated metadata blocks out of automated team/user update scripts
- Resubmitting stored values unchanged is a no-op - diff against current metadata before sending
When it happens
Trigger: POST /team/update, /user/update, or /key/update by a non-proxy-admin (team admin, internal user, app user) whose metadata payload sets estimated_output_tokens or estimated_output_tokens_per_model to values different from the ones already stored on the entity.
Common situations: A team admin self-serving via the UI or API tries to raise estimated_output_tokens to make the rate limiter reserve more (or fewer) tokens for their team; automation scripts that copy admin-curated metadata blocks into update calls and accidentally alter the values; after an admin sets the fields, a non-admin edit that touches other metadata but rewrites these fields with stale/different values.
Related errors
- Only proxy admins, team admins, or org admins can call {rout
- User does not have permission to update this user. Only PROX
- Team not found for team_id={data.team_id}. Non-admin users c
- {reserved_field} is immutable once set and cannot be changed
- User={change_initiated_by.user_id} is not a Proxy Admin or T
AI-assisted analysis of BerriAI/litellm@77b7c6c40c (2026-08-18).
Data as JSON: /api/errors/1d381021b6655d1e.
Report an issue: GitHub.