BerriAI/litellm · error · ProxyException

Client-side 'metadata.tags' not allowed in request. 'reject_

Error message

Client-side 'metadata.tags' not allowed in request. 'reject_clientside_metadata_tags'={general_settings['reject_clientside_metadata_tags']}. Tags can only be set via API key metadata.

What it means

Raised by _reject_clientside_metadata_tags_check when general_settings.reject_clientside_metadata_tags is true and a POST to an LLM route carries metadata.tags in the body. With this flag, tags become server-managed: they may only be attached to virtual keys via key metadata, never injected by the caller, so clients cannot spoof cost grouping.

Source

Thrown at litellm/proxy/auth/auth_checks.py:537

    is_mcp_route: Final = route in LiteLLMRoutes.mcp_routes.value or RouteChecks.check_route_access(
        route=route, allowed_routes=LiteLLMRoutes.mcp_routes.value
    )

    if is_post_method and is_openai_route and not is_mcp_route and "user" not in request_body:
        raise Exception(f"'user' param not passed in. 'enforce_user_param'={general_settings['enforce_user_param']}")


def _reject_clientside_metadata_tags_check(general_settings: dict, request_body: dict, route: str) -> None:
    if not general_settings.get("reject_clientside_metadata_tags", False):
        return

    if (
        RouteChecks.is_llm_api_route(route=route)
        and "metadata" in request_body
        and isinstance(request_body["metadata"], dict)
        and "tags" in request_body["metadata"]
    ):
        raise ProxyException(
            message=f"Client-side 'metadata.tags' not allowed in request. 'reject_clientside_metadata_tags'={general_settings['reject_clientside_metadata_tags']}. Tags can only be set via API key metadata.",
            type=ProxyErrorTypes.bad_request_error,
            param="metadata.tags",
            code=status.HTTP_400_BAD_REQUEST,
        )


def _global_proxy_budget_check(global_proxy_spend: float | None, skip_budget_checks: bool, route: str) -> None:
    if (
        litellm.max_budget > 0
        and not skip_budget_checks
        and global_proxy_spend is not None
        and RouteChecks.is_llm_api_route(route=route)
        and route != "/v1/models"
        and route != "/models"
    ):
        if math.isfinite(litellm.max_budget) and global_proxy_spend > litellm.max_budget:
            raise litellm.BudgetExceededError(

View on GitHub (pinned to 77b7c6c40c)

Solutions

  1. Remove metadata.tags from the client request payload
  2. Set the tags on the virtual key instead: POST /key/update with metadata.tags for that key
  3. Keep any other metadata you need, just drop the tags key from the dict

Example fix

# before
body = {"model": "gpt-4o", "messages": [...], "metadata": {"tags": ["team-a", "prod"]]}

# after
body = {"model": "gpt-4o", "messages": [...], "metadata": {"request_id": "abc"}}
# tags live on the key: curl -X POST /key/update -d '{"key": "sk-...", "metadata": {"tags": ["team-a", "prod"]}}'
Defensive patterns

Strategy: validation

Validate before calling

def strip_client_tags(body: dict) -> dict:
    metadata = body.get("metadata")
    if isinstance(metadata, dict):
        metadata.pop("tags", None)
        if not metadata:
            body.pop("metadata")
    return body

payload = strip_client_tags(payload)

Try / catch

try:
    resp = client.chat.completions.create(**payload)
except Exception as e:
    if "reject_clientside_metadata_tags" in str(e):
        payload = strip_client_tags(payload)
        resp = client.chat.completions.create(**payload)
    else:
        raise

Prevention

When it happens

Trigger: Flag enabled in config, then any request containing {"metadata": {"tags": ["team-a"]}} in the body hits /v1/chat/completions or another LLM route. Only dict-valued metadata containing a tags key is rejected.

Common situations: After the operator enables the flag, LangChain callbacks or internal dashboards that tag requests client-side for cost reporting start getting 400s; shared SDK code sends metadata.tags by default.

Related errors


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