BerriAI/litellm · warning · HTTPException

Malformed request. No keys passed in.

Error message

Malformed request. No keys passed in.

What it means

POST /v2/key/info requires a KeyRequest body containing keys (and/or key_aliases) to look up. When the parsed body is None — request posted with no JSON body at all, or a body FastAPI could not bind — the handler explicitly returns 422 'Malformed request. No keys passed in.' rather than querying with an unbounded (token=None) filter.

Source

Thrown at litellm/proxy/management_endpoints/key_management_endpoints.py:3554

    Returns:
        Dict containing the key and its associated information

    Example Curl:
    ```
    curl -X GET "http://0.0.0.0:4000/key/info" \
    -H "Authorization: Bearer sk-1234" \
    -d {"keys": ["sk-1", "sk-2", "sk-3"]}
    ```
    """
    from litellm.proxy.proxy_server import prisma_client, user_api_key_cache

    try:
        if prisma_client is None:
            raise Exception(
                "Database not connected. Connect a database to your proxy - https://docs.litellm.ai/docs/simple_proxy#managing-auth---virtual-keys"
            )
        if data is None:
            raise HTTPException(
                status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
                detail={"message": "Malformed request. No keys passed in."},
            )

        # Resolve key_aliases to tokens so we never pass token=None (unbounded query)
        tokens_to_query: Final = list(data.keys) if data.keys else []
        if data.key_aliases:
            alias_rows: Final = await _prisma_table(VerificationTokenRepository(prisma_client)).find_many(
                where={"key_alias": {"in": data.key_aliases}},
                include={"litellm_budget_table": True},
            )
            alias_tokens: Final = [row.token for row in alias_rows if row.token]
            tokens_to_query.extend(alias_tokens)

        if not tokens_to_query:
            return {"key": data.keys, "info": []}

        key_info: Final = await prisma_client.get_data(token=tokens_to_query, table_name="key", query_type="find_all")

View on GitHub (pinned to 77b7c6c40c)

Solutions

  1. Send a JSON body: {"keys": ["sk-1", "sk-2"]} (or key_aliases).
  2. Ensure Content-Type: application/json is set by your client.
  3. Short-circuit your calling code when the key list is empty instead of issuing the request.

Example fix

# before
client.post("/v2/key/info")

# after
client.post("/v2/key/info", json={"keys": ["sk-1", "sk-2"]})
Defensive patterns

Strategy: validation

Validate before calling

def build_key_info_body(keys: list[str] | None, key_aliases: list[str] | None) -> dict:
    if not keys and not key_aliases:
        raise ValueError("/v2/key/info needs non-empty 'keys' or 'key_aliases'")
    body: dict = {}
    if keys:
        body["keys"] = keys
    if key_aliases:
        body["key_aliases"] = key_aliases
    return body

Type guard

def is_valid_key_info_request(keys: list[str] | None, key_aliases: list[str] | None) -> bool:
    return bool(keys) or bool(key_aliases)

Prevention

When it happens

Trigger: POST /v2/key/info with no body, an empty JSON object, or a Content-Type that doesn't parse as JSON; client code conditionally omitting the keys field and serializing {}.

Common situations: A 'get info for selected keys' feature where none are selected; forgetting -d/--data in curl; sending the keys as query params instead of the JSON body; upstream proxy/load balancer stripping the request body.

Understand the failure class

Related errors


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