unclecode/crawl4ai · error · HTTPException
str(e)
Error message
str(e)
What it means
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.
Source
Thrown at deploy/docker/api.py:183
# Provider by name only; base_url/api_token are server-derived. A
# request-supplied base_url is ignored (it was the key-exfil vector).
from llm_broker import resolve_llm
llm = resolve_llm(config, provider)
response = perform_completion_with_backoff(
provider=llm["provider"],
prompt_with_variables=prompt,
api_token=llm["api_token"],
temperature=temperature or llm["temperature"],
base_url=llm["base_url"],
base_delay=config["llm"].get("backoff_base_delay", 2),
max_attempts=config["llm"].get("backoff_max_attempts", 3),
exponential_factor=config["llm"].get("backoff_exponential_factor", 2)
)
return response.choices[0].message.content
except LLMProviderNotAllowed as e:
raise HTTPException(status_code=400, detail=str(e))
except Exception as e:
logger.error(f"QA processing error: {str(e)}", exc_info=True)
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=str(e)
)
finally:
if crawler:
await release_crawler(crawler)
async def process_llm_extraction(
redis: aioredis.Redis,
config: dict,
task_id: str,
url: str,
instruction: str,
schema: Optional[str] = None,
cache: str = "0",View on GitHub (pinned to 7e80152142)
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.
Example fix
# before
body = {"provider": "GPT4", "query": q, "url": u} # 400
# after
body = {"provider": "openai/gpt-4o-mini", "query": q, "url": u} # exact allow-listed name
r = requests.post(f"{base}/q", json=body)
r.raise_for_status() Defensive patterns
Strategy: validation
Validate before calling
ALLOWED_PROVIDERS = {"openai/gpt-4o-mini", "gemini/gemini-1.5-pro", "deepseek/deepseek-chat"}
def provider_allowed(provider: str) -> bool:
return provider in ALLOWED_PROVIDERS # keep in sync with server config Type guard
def is_allowed_provider(provider: str | None) -> bool:
return isinstance(provider, str) and provider in ALLOWED_PROVIDERS Try / catch
r = await client.post("/q", json=body)
if r.status_code == 400:
detail = r.json().get("detail", "")
if "not allowed" in detail.lower():
body["provider"] = DEFAULT_PROVIDER # fall back to known-good
r = await client.post("/q", json=body) Prevention
- Pin provider names to a config constant shared with the server allow-list
- Prefer omitting provider to use the server default when unsure
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- error_msg
- Container not found: ${config.container_selector}
- str(e)
- Invalid status: {status}. Must be one of: all, active, compl
- Invalid limit: {limit}. Must be between 1 and 1000
AI-assisted analysis of unclecode/crawl4ai@7e80152142 (2026-08-14).
Data as JSON: /api/errors/8902a2de6feeaee4.
Report an issue: GitHub.