{"record":{"id":"3dc9175d9391b827","repo":"ruvnet/RuView","slug":"page-must-be-1","errorCode":null,"errorMessage":"Page must be >= 1","messagePattern":"Page must be >= 1","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"warning","filePath":"archive/v1/src/api/dependencies.py","lineNumber":338,"sourceCode":"\ndef get_router_config(router_id: str = Depends(validate_router_access)):\n    \"\"\"Get router configuration.\"\"\"\n    domain_config = get_domain_config()\n    return domain_config.get_router(router_id)\n\n\n# Pagination dependencies\nclass PaginationParams:\n    \"\"\"Pagination parameters.\"\"\"\n    \n    def __init__(\n        self,\n        page: int = 1,\n        size: int = 20,\n        max_size: int = 100\n    ):\n        if page < 1:\n            raise HTTPException(\n                status_code=status.HTTP_400_BAD_REQUEST,\n                detail=\"Page must be >= 1\"\n            )\n        \n        if size < 1:\n            raise HTTPException(\n                status_code=status.HTTP_400_BAD_REQUEST,\n                detail=\"Size must be >= 1\"\n            )\n        \n        if size > max_size:\n            raise HTTPException(\n                status_code=status.HTTP_400_BAD_REQUEST,\n                detail=f\"Size must be <= {max_size}\"\n            )\n        \n        self.page = page\n        self.size = size","sourceCodeStart":320,"sourceCodeEnd":356,"githubUrl":"https://github.com/ruvnet/RuView/blob/4685618388a5e49fad5b3005806f3bdd6a7c25c3/archive/v1/src/api/dependencies.py#L320-L356","documentation":"Raised as HTTP 400 by the PaginationParams dependency in archive/v1/src/api/dependencies.py:338 when the `page` query parameter is less than 1. The API is 1-based because the offset is computed as (page - 1) * size, so page < 1 would yield a negative offset. Since this check runs inside a FastAPI dependency, the request is rejected before any route handler executes.","triggerScenarios":"Calling any paginated list endpoint wired with Depends(get_pagination_params) while passing ?page=0 or a negative page, e.g. GET /api/v1/detections?page=0&size=20.","commonSituations":"A JS frontend forwards its 0-based UI index unchanged; a 'previous page' handler decrements below 1 on the first page; `page` is derived from an unset variable that defaults to 0 before being sent.","solutions":["Send page=1 — the first page is 1, not 0","Clamp the value in the client before every request: page = max(1, currentPage)","If you own the server, clamp inside the dependency instead of raising so legacy clients keep working"],"exampleFix":"// before\nconst res = await fetch(`/api/v1/detections?page=${pageIdx}&size=20`);\n\n// after\nconst page = Math.max(1, pageIdx + 1); // UI index is 0-based, API is 1-based\nconst res = await fetch(`/api/v1/detections?page=${page}&size=20`);","handlingStrategy":"validation","validationCode":"def build_pagination(page, size, max_size=100):\n    page = max(1, int(page if page is not None else 1))\n    size = max(1, min(int(size if size is not None else 20), max_size))\n    return {'page': page, 'size': size}\n\nparams = build_pagination(ui_page, ui_size)","typeGuard":"def is_valid_page(page) -> bool:\n    return isinstance(page, int) and not isinstance(page, bool) and page >= 1","tryCatchPattern":"resp = await client.get('/api/v1/detections', params=params)\nif resp.status_code == 400 and 'Page must be' in resp.json().get('detail', ''):\n    params['page'] = 1\n    resp = await client.get('/api/v1/detections', params=params)\nresp.raise_for_status()","preventionTips":["Treat the API as 1-based; convert UI indices before sending","Build query strings in one shared helper so clamps cannot be bypassed","Unit-test the client with page=0 and size=0 inputs"],"tags":["python","fastapi","pagination","validation","http-400"],"backgroundTag":null,"analyzedSha":"4685618388a5e49fad5b3005806f3bdd6a7c25c3","analyzedAt":"2026-08-16T06:09:40.886Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}