lfnovo/open-notebook · warning · HTTPException

Invalid order_by: '{order_by}'. Allowed fields: {', '.join(s

Error message

Invalid order_by: '{order_by}'. Allowed fields: {', '.join(sorted(allowed_fields))}. Allowed directions: asc, desc

What it means

400 from GET /api/v1/notebooks when order_by has two words but either the field is not allowed or the direction is not 'asc'/'desc'. Note the direction check is case-sensitive on the lowercased parts only insofar as the whole string was lowercased, so 'Created ASC' works but 'created ascending' or 'name descending' fails.

Source

Thrown at api/routers/notebooks.py:82

    order_by: str = Query("updated desc", description="Order by field and direction"),
):
    """Get all notebooks with optional filtering and ordering."""
    try:
        # Validate order_by against allowlist to prevent SurrealQL injection
        allowed_fields = {"name", "created", "updated"}
        allowed_directions = {"asc", "desc"}

        parts = order_by.strip().lower().split()
        if len(parts) == 1:
            if parts[0] not in allowed_fields:
                raise HTTPException(
                    status_code=400,
                    detail=f"Invalid order_by field: '{order_by}'. Allowed fields: {', '.join(sorted(allowed_fields))}",
                )
            validated_order_by = parts[0]
        elif len(parts) == 2:
            if parts[0] not in allowed_fields or parts[1] not in allowed_directions:
                raise HTTPException(
                    status_code=400,
                    detail=f"Invalid order_by: '{order_by}'. Allowed fields: {', '.join(sorted(allowed_fields))}. Allowed directions: asc, desc",
                )
            validated_order_by = f"{parts[0]} {parts[1]}"
        else:
            raise HTTPException(
                status_code=400,
                detail=f"Invalid order_by format: '{order_by}'. Expected 'field' or 'field direction'",
            )

        # Build the query with counts
        query = f"""
            SELECT *,
            count(<-reference.in) as source_count,
            count(<-artifact.in) as note_count
            FROM notebook
            ORDER BY {validated_order_by}
        """

View on GitHub (pinned to a7de90d38a)

Solutions

  1. Use exactly 'asc' or 'desc' (they're lowercased with the input, so case is safe) after an allowed field
  2. Drop the direction to use the default sort
  3. Validate order_by client-side against the allow-list before calling

Example fix

// before
GET /api/v1/notebooks?order_by=updated descending
// after
GET /api/v1/notebooks?order_by=updated desc
Defensive patterns

Strategy: validation

Validate before calling

const m = order_by.trim().toLowerCase().match(/^(name|created|updated)( (asc|desc))?$/);
if (!m) order_by = 'created desc'; // normalize instead of erroring

Type guard

const isValidOrderBy2 = (s: string) =>
  /^(name|created|updated)( asc| desc)?$/.test(s.trim().toLowerCase());

Try / catch

try { await api.getNotebooks({order_by: q}); } catch (e) { if (e.status === 400 && /Allowed directions/.test(e.detail)) refetchSorted('created desc'); }

Prevention

When it happens

Trigger: GET /notebooks?order_by=created ascending, ?order_by=title desc, or ?order_by=name descending — wrong direction word or disallowed field with a direction.

Common situations: Sort UI sending full direction words; typos in direction; combining a bad field with a valid direction.

Related errors


AI-assisted analysis of lfnovo/open-notebook@a7de90d38a (2026-08-27). Data as JSON: /api/errors/f3a2d646a0f82ad6. Report an issue: GitHub.