{"record":{"id":"8902a2de6feeaee4","repo":"unclecode/crawl4ai","slug":"str-e","errorCode":null,"errorMessage":"str(e)","messagePattern":"str\\(e\\)","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"error","filePath":"deploy/docker/api.py","lineNumber":183,"sourceCode":"\n        # Provider by name only; base_url/api_token are server-derived. A\n        # request-supplied base_url is ignored (it was the key-exfil vector).\n        from llm_broker import resolve_llm\n        llm = resolve_llm(config, provider)\n        response = perform_completion_with_backoff(\n            provider=llm[\"provider\"],\n            prompt_with_variables=prompt,\n            api_token=llm[\"api_token\"],\n            temperature=temperature or llm[\"temperature\"],\n            base_url=llm[\"base_url\"],\n            base_delay=config[\"llm\"].get(\"backoff_base_delay\", 2),\n            max_attempts=config[\"llm\"].get(\"backoff_max_attempts\", 3),\n            exponential_factor=config[\"llm\"].get(\"backoff_exponential_factor\", 2)\n        )\n\n        return response.choices[0].message.content\n    except LLMProviderNotAllowed as e:\n        raise HTTPException(status_code=400, detail=str(e))\n    except Exception as e:\n        logger.error(f\"QA processing error: {str(e)}\", exc_info=True)\n        raise HTTPException(\n            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,\n            detail=str(e)\n        )\n    finally:\n        if crawler:\n            await release_crawler(crawler)\n\nasync def process_llm_extraction(\n    redis: aioredis.Redis,\n    config: dict,\n    task_id: str,\n    url: str,\n    instruction: str,\n    schema: Optional[str] = None,\n    cache: str = \"0\",","sourceCodeStart":165,"sourceCodeEnd":201,"githubUrl":"https://github.com/unclecode/crawl4ai/blob/7e801521428ee12509994d39151006f64055ebe3/deploy/docker/api.py#L165-L201","documentation":"HTTP 400 raised in process_qa (deploy/docker/api.py:183) when LLMProviderNotAllowed escapes — the requested LLM provider string is not on the server's allow-list (provider allowlisting exists because base_url/api_token are server-derived). str(e) carries the specific disallowed-provider reason. This is a client configuration error: change the request, not the server.","triggerScenarios":"POST /qa with provider=\"some-unknown-provider\" or a provider name not present in the server's llm config allow-list; case mismatch (\"OpenAI\" vs \"openai\"); config file updated server-side but client still sending old provider names.","commonSituations":"Deploying the Docker server with a restricted LLM config and clients assuming all upstream providers are available; version drift between client examples and server config schema.","solutions":["Read the 400 detail — it names the provider not allowed and typically the allowed set.","Send an allow-listed provider name exactly as configured (case-sensitive).","If the provider should be available, add it to the server's LLM config allow-list and restart.","Pre-validate provider names against a fetched/configured list before submitting jobs."],"exampleFix":"# before\nbody = {\"provider\": \"GPT4\", \"query\": q, \"url\": u}  # 400\n\n# after\nbody = {\"provider\": \"openai/gpt-4o-mini\", \"query\": q, \"url\": u}  # exact allow-listed name\nr = requests.post(f\"{base}/q\", json=body)\nr.raise_for_status()","handlingStrategy":"validation","validationCode":"ALLOWED_PROVIDERS = {\"openai/gpt-4o-mini\", \"gemini/gemini-1.5-pro\", \"deepseek/deepseek-chat\"}\n\ndef provider_allowed(provider: str) -> bool:\n    return provider in ALLOWED_PROVIDERS  # keep in sync with server config","typeGuard":"def is_allowed_provider(provider: str | None) -> bool:\n    return isinstance(provider, str) and provider in ALLOWED_PROVIDERS","tryCatchPattern":"r = await client.post(\"/q\", json=body)\nif r.status_code == 400:\n    detail = r.json().get(\"detail\", \"\")\n    if \"not allowed\" in detail.lower():\n        body[\"provider\"] = DEFAULT_PROVIDER  # fall back to known-good\n        r = await client.post(\"/q\", json=body)","preventionTips":["Pin provider names to a config constant shared with the server allow-list","Prefer omitting provider to use the server default when unsure"],"tags":["http-400","llm-provider","allowlist","validation"],"backgroundTag":null,"analyzedSha":"7e801521428ee12509994d39151006f64055ebe3","analyzedAt":"2026-08-14T20:46:20.673Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}