{"record":{"id":"d311f54527713842","repo":"jamiepine/voicebox","slug":"limit-must-be-between-1-and-200-d311f5","errorCode":null,"errorMessage":"limit must be between 1 and 200","messagePattern":"limit must be between 1 and 200","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"warning","filePath":"backend/routes/captures.py","lineNumber":77,"sourceCode":"    except Exception as e:\n        logger.exception(\"Failed to create capture\")\n        raise HTTPException(status_code=500, detail=str(e))\n\n    return models.CaptureCreateResponse(\n        **capture.model_dump(),\n        auto_refine=bool(saved.auto_refine),\n        allow_auto_paste=bool(saved.allow_auto_paste),\n    )\n\n\n@router.get(\"/captures\", response_model=models.CaptureListResponse)\nasync def list_captures_endpoint(\n    limit: int = 50,\n    offset: int = 0,\n    db: Session = Depends(get_db),\n):\n    if limit < 1 or limit > 200:\n        raise HTTPException(status_code=400, detail=\"limit must be between 1 and 200\")\n    if offset < 0:\n        raise HTTPException(status_code=400, detail=\"offset must be >= 0\")\n\n    items, total = captures_service.list_captures(db, limit=limit, offset=offset)\n    return models.CaptureListResponse(items=items, total=total)\n\n\n@router.get(\"/captures/{capture_id}\", response_model=models.CaptureResponse)\nasync def get_capture_endpoint(capture_id: str, db: Session = Depends(get_db)):\n    capture = captures_service.get_capture(capture_id, db)\n    if not capture:\n        raise HTTPException(status_code=404, detail=\"Capture not found\")\n    return capture\n\n\n@router.get(\"/captures/{capture_id}/audio\")\nasync def get_capture_audio_endpoint(capture_id: str, db: Session = Depends(get_db)):\n    \"\"\"Stream the original capture audio file.\"\"\"","sourceCodeStart":59,"sourceCodeEnd":95,"githubUrl":"https://github.com/jamiepine/voicebox/blob/51f49dea198384b4eb6087b72c17057c6eb1c1cd/backend/routes/captures.py#L59-L95","documentation":"Returned as a 400 from GET /captures when the `limit` query param is outside [1, 200]. The route hard-validates limit before calling list_captures because the list endpoint must stay bounded — an unbounded or zero limit would either return nothing or attempt to load the entire table. This is a static guard, independent of service logic.","triggerScenarios":"GET /captures?limit=0, GET /captures?limit=201, or GET /captures?limit=-5 (a negative int parsed by FastAPI). Also limit omitted is fine (default 50).","commonSituations":"Frontend pagination bug passing limit=0 to mean 'no items'; a UI control allowing values above 200; a script scraping with limit=1000.","solutions":["Clamp limit to [1, 200] on the client before building the query string.","Use the default (omit limit) for normal list views.","If you genuinely need more than 200, paginate with offset instead of inflating limit."],"exampleFix":"// before\nfetch(`/captures?limit=${requested}`) // requested may be 0 or 1000\n// after\nconst limit = Math.min(200, Math.max(1, requested || 50));\nfetch(`/captures?limit=${limit}`)","handlingStrategy":"validation","validationCode":"const limit = Math.min(200, Math.max(1, Number(reqLimit) || 50));\nfetch(`/captures?limit=${limit}`);","typeGuard":"function isValidLimit(n: number): boolean {\n  return Number.isInteger(n) && n >= 1 && n <= 200;\n}","tryCatchPattern":null,"preventionTips":["Clamp limit to [1, 200] on the client.","For large result sets, paginate with offset instead of raising limit.","Use a shared pagination helper so all list calls apply the same bounds."],"tags":["capture","pagination","validation","query-param","fastapi"],"backgroundTag":null,"analyzedSha":"51f49dea198384b4eb6087b72c17057c6eb1c1cd","analyzedAt":"2026-08-12T16:51:42.824Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}