BerriAI/litellm · error · HTTPException

Invalid sort order. Must be 'asc' or 'desc'

Error message

Invalid sort order. Must be 'asc' or 'desc'

What it means

Raised by the LiteLLM proxy key-management endpoints when a list request supplies a sort_order value that is not 'asc' or 'desc'. The helper that builds the Prisma order_by clause validates the column first (sort_by) and then lower-cases sort_order and checks membership in ['asc','desc']; anything else gets an HTTP 400 before any query runs. It exists to prevent arbitrary strings from reaching the database layer.

Source

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

        return None
    # Validate sort_by is a valid column
    valid_columns: Final = [
        "spend",
        "max_budget",
        "created_at",
        "updated_at",
        "token",
        "key_alias",
    ]
    if sort_by not in valid_columns:
        raise HTTPException(
            status_code=400,
            detail={"error": f"Invalid sort column. Must be one of: {', '.join(valid_columns)}"},
        )

    # Validate sort_order
    if sort_order.lower() not in ["asc", "desc"]:
        raise HTTPException(
            status_code=400,
            detail={"error": "Invalid sort order. Must be 'asc' or 'desc'"},
        )

    order_by[sort_by] = sort_order.lower()

    return order_by


def _build_expires_where_clause(expires_filter: str, now: datetime) -> dict[str, object]:
    if expires_filter == "expired":
        return {"AND": [{"expires": {"not": None}}, {"expires": {"lt": now}}]}
    return {"OR": [{"expires": None}, {"expires": {"gte": now}}]}


def _build_key_filter_conditions(
    user_id: str | None,
    team_id: str | None,

View on GitHub (pinned to 77b7c6c40c)

Solutions

  1. Send exactly 'asc' or 'desc' (any letter casing) for sort_order, e.g. /key/list?sort_by=token&sort_order=desc
  2. Fix the UI/SDK mapping so 'ascending'->'asc' and 'descending'->'desc' before the request leaves the client
  3. URL-encode the query string properly and confirm no stray whitespace or newlines are appended
  4. If the 400 mentions the column instead, also verify sort_by is one of the valid columns listed in the error detail (includes token, key_alias, updated_at, etc.)

Example fix

# before
curl 'http://localhost:4000/key/list?sort_by=token&sort_order=ascending'
# after
curl 'http://localhost:4000/key/list?sort_by=token&sort_order=desc'   # 'ASC'/'Desc' also OK; server lower-cases
Defensive patterns

Strategy: validation

Validate before calling

SORT_ORDERS = ('asc', 'desc')

def normalize_list_params(params: dict) -> dict:
    if 'sort_order' in params:
        so = str(params['sort_order']).strip().lower()
        if so not in SORT_ORDERS:
            raise ValueError(f"sort_order must be one of {SORT_ORDERS}, got {params['sort_order']!r}")
        params['sort_order'] = so
    return params

Type guard

def is_valid_sort_order(value: object) -> bool:
    return isinstance(value, str) and value.strip().lower() in ('asc', 'desc')

Try / catch

try:
    r = await client.get('/key/list', params=normalize_list_params(q))
    r.raise_for_status()
except httpx.HTTPStatusError as e:
    if e.response.status_code == 400 and 'sort order' in e.response.text.lower():
        q['sort_order'] = 'desc'          # correct and retry once
        r = await client.get('/key/list', params=q)
    else:
        raise

Prevention

When it happens

Trigger: Calling GET /key/list (or any key-listing route that accepts sort_by/sort_order) with e.g. sort_order=ascending, sort_order=ASCENDING, sort_order='' (empty), sort_order='DESC ' (trailing whitespace), or a URL-mangled value like sort_order=desc%0A. Case-insensitive: 'ASC'/'Desc' pass because the check applies .lower() first.

Common situations: Frontend dropdowns that send 'ascending'/'descending' instead of 'asc'/'desc'; copy-pasted query strings from other APIs (e.g. Grafana-style 'ASC' is fine but '+desc' from unencoded spaces is not); typos like 'ascc' or 'dsc'; SDKs defaulting to 'descending' when no explicit value is set.

Related errors


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