{"record":{"id":"e3e3c1e3d01024f8","repo":"unclecode/crawl4ai","slug":"invalid-metric-metric-must-be-one-of-memory","errorCode":null,"errorMessage":"Invalid metric: {metric}. Must be one of: memory, requests, browsers","messagePattern":"Invalid metric: (.+?)\\. Must be one of: memory, requests, browsers","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"warning","filePath":"deploy/docker/monitor_routes.py","lineNumber":109,"sourceCode":"    try:\n        monitor = get_monitor()\n        return monitor.get_endpoint_stats_summary()\n    except Exception as e:\n        logger.error(f\"Error getting endpoint stats: {e}\")\n        raise HTTPException(500, str(e))\n\n\n@router.get(\"/timeline\")\nasync def get_timeline(metric: str = \"memory\", window: str = \"5m\"):\n    \"\"\"Get timeline data for charts.\n\n    Args:\n        metric: 'memory', 'requests', or 'browsers'\n        window: Time window (only '5m' supported for now)\n    \"\"\"\n    # Input validation\n    if metric not in [\"memory\", \"requests\", \"browsers\"]:\n        raise HTTPException(400, f\"Invalid metric: {metric}. Must be one of: memory, requests, browsers\")\n    if window != \"5m\":\n        raise HTTPException(400, f\"Invalid window: {window}. Only '5m' is currently supported\")\n\n    try:\n        monitor = get_monitor()\n        return monitor.get_timeline_data(metric, window)\n    except Exception as e:\n        logger.error(f\"Error getting timeline: {e}\")\n        raise HTTPException(500, str(e))\n\n\n@router.get(\"/logs/janitor\")\nasync def get_janitor_log(limit: int = 100):\n    \"\"\"Get recent janitor cleanup events.\"\"\"\n    # Input validation\n    if limit < 1 or limit > 1000:\n        raise HTTPException(400, f\"Invalid limit: {limit}. Must be between 1 and 1000\")\n","sourceCodeStart":91,"sourceCodeEnd":127,"githubUrl":"https://github.com/unclecode/crawl4ai/blob/7e801521428ee12509994d39151006f64055ebe3/deploy/docker/monitor_routes.py#L91-L127","documentation":"An explicit 400 validation error raised by GET /monitor/timeline when the metric query parameter is not one of the three supported values: 'memory', 'requests', or 'browsers'. This is intentional allow-list validation of the metric dimension used to select which timeline series the monitor returns; anything else is rejected before any data access happens.","triggerScenarios":"Calling GET /monitor/timeline?metric=cpu or ?metric=mem (typo/abbreviation), passing an empty value (?metric=), or a dashboard passing a newer metric name the server does not support yet.","commonSituations":"Frontend chart component built against a newer API that added metrics (e.g. 'latency') while the deployed server only supports three; URL-encoding issues producing an unexpected string; copy-paste from docs that list a metric not yet implemented.","solutions":["Use one of the exact supported values: metric=memory, metric=requests, or metric=browsers (they are case-sensitive).","If a dashboard needs another metric, extend the allow-list and monitor.get_timeline_data() server-side first, then update the client.","Add a client-side enum/dropdown constrained to the three values so invalid requests cannot be issued."],"exampleFix":"# before\nconst url = `/monitor/timeline?metric=${selected}`; // selected = 'cpu'\n\n# after\nconst ALLOWED = ['memory', 'requests', 'browsers'];\nconst metric = ALLOWED.includes(selected) ? selected : 'memory';\nconst url = `/monitor/timeline?metric=${metric}&window=5m`;","handlingStrategy":"validation","validationCode":"ALLOWED_METRICS = {'memory', 'requests', 'browsers'}\n\ndef valid_metric(m: str) -> bool:\n    return m in ALLOWED_METRICS\n\nurl = f\"/monitor/timeline?metric={m if valid_metric(m) else 'memory'}\"","typeGuard":"from typing import Literal\n\nMetric = Literal['memory', 'requests', 'browsers']\n\ndef is_metric(v: str) -> TypeGuard[Metric]:\n    return v in ('memory', 'requests', 'browsers')","tryCatchPattern":"try:\n    resp = get(f\"{base}/monitor/timeline?metric={metric}\")\nexcept HTTPError as e:\n    if e.response.status_code == 400:\n        raise ValueError(f'unsupported metric {metric!r}; use memory|requests|browsers') from e\n    raise","preventionTips":["Drive the metric selector from a fixed dropdown, not free text.","Pin the client to the server's supported metric list; update both together when adding metrics.","Handle 400 by falling back to metric=memory rather than failing the chart."],"tags":["fastapi","monitoring","validation","query-params"],"backgroundTag":null,"analyzedSha":"7e801521428ee12509994d39151006f64055ebe3","analyzedAt":"2026-08-14T20:46:20.673Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}