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

  1. Have a proxy admin (role proxy_admin) make the change, since only that role may set these fields
  2. Remove estimated_output_tokens / estimated_output_tokens_per_model from the update payload if the caller is not a proxy admin
  3. 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

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


AI-assisted analysis of BerriAI/litellm@77b7c6c40c (2026-08-18). Data as JSON: /api/errors/1d381021b6655d1e. Report an issue: GitHub.