{"record":{"id":"bdc921fa9c55c95b","repo":"BerriAI/litellm","slug":"urn-litellm-error-unknown-query-parameter-bdc921","errorCode":"urn:litellm:error:unknown-query-parameter","errorMessage":"Unrecognized query parameter(s): {', '.join(unknown)}.","messagePattern":"Unrecognized query parameter\\(s\\): (.+?)\\.","errorType":"http","errorClass":"ManagementProblem","httpStatus":400,"severity":"warning","filePath":"litellm/proxy/management_endpoints/management_v1/list_framework.py","lineNumber":515,"sourceCode":"        take=page_size,\n    )\n\n\ndef _duplicate_params(request: Request) -> tuple[str, ...]:\n    names: Final = tuple(name for name, _ in request.query_params.multi_items())\n    return tuple(sorted(frozenset(name for name in names if names.count(name) > 1)))\n\n\nasync def handle_list(\n    spec: ListSpec[TRow, TOut],\n    executor: ListExecutor[TRow],\n    request: Request,\n    caller: UserAPIKeyAuth,\n) -> ListResponse[TOut]:\n    \"\"\"Plan, execute, count, serialize, envelope. Failures reach the client as RFC 9457 problems.\"\"\"\n    plan: Final = build_query_plan(spec=spec, params=request.query_params, caller=caller)\n    if isinstance(plan, ProblemDetail):\n        raise ManagementProblem(plan)\n\n    # Checked here rather than in build_query_plan because a Mapping[str, str] cannot\n    # represent a repeat: query_params.get() silently keeps the last one, so ?page=1&page=999\n    # would page from 999 without the caller ever being told which value won.\n    duplicates: Final = _duplicate_params(request)\n    if duplicates:\n        raise ManagementProblem(\n            _problem(\n                \"duplicate-query-parameter\",\n                \"Duplicate query parameter\",\n                400,\n                f\"Repeated query parameter(s): {', '.join(duplicates)}. Each may appear once; \"\n                f\"use a comma-separated list for multiple sort keys or filter values.\",\n            )\n        )\n\n    total_count: Final = await executor.count(plan.where)\n    rows: Final = await executor.find_many(plan)","sourceCodeStart":497,"sourceCodeEnd":533,"githubUrl":"https://github.com/BerriAI/litellm/blob/77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8/litellm/proxy/management_endpoints/management_v1/list_framework.py#L497-L533","documentation":"The same unknown-query-parameter problem, raised from the shared list framework: handle_list() first builds a query plan via build_query_plan, which returns a ProblemDetail when it finds query names outside the spec's allowed set (including the route's scoping params), and line 515 converts that plan-problem into a ManagementProblem. Every /management/v1 list endpoint built on handle_list (budgets, keys, teams, ...) funnels through this single raise.","triggerScenarios":"Any management/v1 list call with an undeclared parameter, e.g. ?sort_dir=asc instead of sort=..., ?model=gpt-4o on a route without a model filter, or a caller trying to override a server-scoped param like user_filter that only admins can set; also fires when params pass the route-level check but not the per-spec allowed set.","commonSituations":"Clients targeting params documented for a different list endpoint; assuming OpenAI-style pagination names (limit/offset) instead of page/page_size; version drift between the SDK's query builder and the deployed spec.","solutions":["Send only parameters the endpoint declares: page, page_size, sort (comma-separated, '-' prefix for descending), search, and filter[<field>][op] names from its spec","Read the problem's detail — it lists the unknown names and the allowed set for that exact route","Pin your client SDK to a version matching the proxy release so allowed sets agree","Never repeat a parameter (?page=1&page=2) — that raises the related duplicate-query-parameter 400 from the same handler"],"exampleFix":"# before\ncurl 'http://localhost:4000/management/v1/keys?sort_dir=asc'   # 400 via handle_list\n# after\ncurl 'http://localhost:4000/management/v1/keys?sort=-token&page_size=25'","handlingStrategy":"validation","validationCode":"from collections import Counter\n\ndef validate_list_query(q: dict) -> None:\n    ALLOWED = {'page', 'page_size', 'sort', 'search'}  # per-route allowlist\n    unknown = set(q) - ALLOWED\n    if unknown:\n        raise ValueError(f'unknown params {sorted(unknown)}; allowed {sorted(ALLOWED)}')\n    dupes = [k for k, n in Counter(q).items() if n > 1]  # only relevant if you build raw query strings\n    if dupes:\n        raise ValueError(f'params may appear once: {dupes}; use comma-separated values instead')","typeGuard":null,"tryCatchPattern":"try:\n    r = await client.get('/management/v1/keys', params=q)\n    r.raise_for_status()\nexcept httpx.HTTPStatusError as e:\n    problem = e.response.json()\n    ptype = problem.get('type', '')\n    if ptype.endswith('unknown-query-parameter'):\n        q = {k: v for k, v in q.items() if k in ALLOWED}  # strip and retry once\n        r = await client.get('/management/v1/keys', params=q)\n    elif ptype.endswith('duplicate-query-parameter'):\n        raise ValueError('send each param once; join multi-values with commas') from e\n    else:\n        raise","preventionTips":["Build URLs with a params dict — httpx/requests cannot accidentally duplicate keys that way","Learn the framework's contract: page/page_size, sort='-field,field2', filter[field][op]=v, search=q","Never forward arbitrary user-supplied query strings into management/v1 calls; filter them first"],"tags":["query-params","validation","problem-details","litellm-proxy","management-api","pagination"],"backgroundTag":"unknown-query-parameter","analyzedSha":"77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8","analyzedAt":"2026-08-18T11:44:31.656Z","schemaVersion":2},"datasetVersion":"2026-08-21T13:17:26.733Z"}