{"record":{"id":"5b2e2edc30010372","repo":"unclecode/crawl4ai","slug":"query-parameter-q-is-required","errorCode":null,"errorMessage":"Query parameter 'q' is required","messagePattern":"Query parameter 'q' is required","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"error","filePath":"deploy/docker/server.py","lineNumber":821,"sourceCode":"        raise HTTPException(500, detail=str(e))\n    finally:\n        if crawler:\n            await release_crawler(crawler)\n\n\n@app.get(\"/llm/{url:path}\")\nasync def llm_endpoint(\n    request: Request,\n    url: str = Path(...),\n    q: str = Query(...),\n    provider: Optional[str] = Query(None, description=\"LLM provider override, e.g. 'openai/gpt-4o-mini'\"),\n    temperature: Optional[float] = Query(None, description=\"LLM temperature override\"),\n    _td: Dict = Depends(token_dep),\n):\n    # base_url is intentionally not accepted (key-exfil vector); the endpoint is\n    # derived server-side from the provider name only.\n    if not q:\n        raise HTTPException(400, \"Query parameter 'q' is required\")\n    if not url.startswith((\"http://\", \"https://\")) and not url.startswith((\"raw:\", \"raw://\")):\n        url = \"https://\" + url\n    answer = await handle_llm_qa(url, q, config, provider=provider, temperature=temperature)\n    return JSONResponse({\"answer\": answer})\n\n\n@app.get(\"/schema\")\nasync def get_schema():\n    from crawl4ai import BrowserConfig, CrawlerRunConfig\n    return {\"browser\": BrowserConfig().dump(),\n            \"crawler\": CrawlerRunConfig().dump()}\n\n\n@app.get(\"/hooks/info\")\nasync def get_hooks_info():\n    \"\"\"Enumerate the available declarative hook actions and their parameter schemas.\n\n    Arbitrary hook code is no longer accepted (it was an exec()-based RCE","sourceCodeStart":803,"sourceCodeEnd":839,"githubUrl":"https://github.com/unclecode/crawl4ai/blob/7e801521428ee12509994d39151006f64055ebe3/deploy/docker/server.py#L803-L839","documentation":"A 400 from GET /llm/{url}: the required query parameter q (the question for LLM-based QA over the page) was missing, empty, or blank. The route declares q: str = Query(...) so FastAPI normally enforces presence, but an empty string ('?q=') passes FastAPI and is caught by this explicit `if not q` check.","triggerScenarios":"GET /llm/https://example.com/page without a q parameter (FastAPI 422 normally) or with q= (empty value → this 400). Also q=%20 (whitespace-only still truthy, so passes — only truly empty values hit this).","commonSituations":"Clients building the query string manually and dropping q when the user asked no question; template strings leaving q empty; URL-encoding bugs that truncate parameters.","solutions":["Include a non-empty q: GET /llm/https://example.com/page?q=summarize%20the%20article.","Client-side, refuse to call the endpoint when the question string is empty.","If you see 422 instead, the parameter was absent entirely — same fix."],"exampleFix":"# before\nrequests.get(f'{base}/llm/{url}')\n# after\nif not q.strip():\n    raise ValueError('question required')\nrequests.get(f'{base}/llm/{url}', params={'q': q})","handlingStrategy":"validation","validationCode":"from urllib.parse import urlencode\n\ndef llm_url(base: str, url: str, q: str) -> str:\n    if not q or not q.strip():\n        raise ValueError(\"query 'q' must be a non-empty question\")\n    return f'{base}/llm/{url}?{urlencode({\"q\": q})}'","typeGuard":"def has_question(q: str | None) -> bool:\n    return isinstance(q, str) and len(q.strip()) > 0","tryCatchPattern":null,"preventionTips":["Validate q client-side before building the URL.","Use params= with an HTTP client instead of hand-concatenating query strings.","Treat both 400 (empty q) and 422 (missing q) as the same fix: supply a real question."],"tags":["llm","http-400","query-params","validation"],"backgroundTag":null,"analyzedSha":"7e801521428ee12509994d39151006f64055ebe3","analyzedAt":"2026-08-14T20:46:20.673Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}