lfnovo/open-notebook · warning · HTTPException

Invalid order_by field: '{order_by}'. Allowed fields: {', '.

Error message

Invalid order_by field: '{order_by}'. Allowed fields: {', '.join(sorted(allowed_fields))}

What it means

400 from GET /api/v1/notebooks when the single-word order_by query param's field is not in {name, created, updated}. The router validates order_by manually: lowercased, split on whitespace, first token must be an allowed field.

Source

Thrown at api/routers/notebooks.py:75

        last_viewed_at=str(row.get("last_viewed_at", "")),
    )


@router.get("/notebooks", response_model=List[NotebookResponse])
async def get_notebooks(
    archived: Optional[bool] = Query(None, description="Filter by archived status"),
    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

View on GitHub (pinned to a7de90d38a)

Solutions

  1. Use only name, created, or updated as the field
  2. Optional direction is supported as second word: 'created desc'
  3. Map UI sort keys to these three fields in the client

Example fix

// before
GET /api/v1/notebooks?order_by=title
// after
GET /api/v1/notebooks?order_by=name desc
Defensive patterns

Strategy: validation

Validate before calling

const FIELDS = ['name','created','updated'];
const parts = orderBy.trim().toLowerCase().split(/\s+/);
if (!FIELDS.includes(parts[0])) throw new Error(`order_by field must be one of ${FIELDS.join(', ')}`);

Type guard

const isValidOrderBy = (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 && /order_by/.test(e.detail)) refetchSorted('created'); }

Prevention

When it happens

Trigger: GET /notebooks?order_by=title, ?order_by=Name (actually works because of .lower(), but 'title', 'id', 'notebook' fail), or any single word not in the allow-list.

Common situations: Frontend sort dropdown using a column key that isn't one of the three allowed fields; copied API examples with wrong field names.

Related errors


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