{"record":{"id":"0a99a6cc1c5b58d4","repo":"unclecode/crawl4ai","slug":"llm-provider-not-allowed","errorCode":null,"errorMessage":"LLM provider not allowed","messagePattern":"LLM provider not allowed","errorType":"http","errorClass":"LLMProviderNotAllowed","httpStatus":400,"severity":"error","filePath":"deploy/docker/llm_broker.py","lineNumber":57,"sourceCode":"        fams.add(_family(cfg[\"provider\"]))\n    return {f for f in fams if f}\n\n\ndef resolve_llm(config: Dict, requested_provider: Optional[str] = None) -> Dict:\n    \"\"\"Resolve the LLM call parameters fully server-side.\n\n    A request-supplied base_url/api_token is intentionally NOT a parameter here:\n    callers pass only the provider *name*. Returns {provider, base_url,\n    api_token, temperature}, all server-derived.\n    \"\"\"\n    from utils import get_llm_api_key, get_llm_base_url, get_llm_temperature\n\n    default = config[\"llm\"][\"provider\"]\n    provider = requested_provider or default\n\n    fams = allowed_provider_families(config)\n    if fams and _family(provider) not in fams:\n        raise LLMProviderNotAllowed(\"LLM provider not allowed\")\n\n    return {\n        \"provider\": provider,\n        \"base_url\": get_llm_base_url(config, provider),   # canonical, never from request\n        \"api_token\": get_llm_api_key(config, provider),   # server credential\n        \"temperature\": get_llm_temperature(config, provider),\n    }\n","sourceCodeStart":39,"sourceCodeEnd":65,"githubUrl":"https://github.com/unclecode/crawl4ai/blob/7e801521428ee12509994d39151006f64055ebe3/deploy/docker/llm_broker.py#L39-L65","documentation":"resolve_llm in deploy/docker/llm_broker.py raises LLMProviderNotAllowed when the requested provider's family (per _family) is not in allowed_provider_families(config) — the allowlist derived from server config (providers with configured keys plus explicit allow rules). Credentials are server-derived only; a request may pick a provider name but never a base_url/token, so an unapproved provider is rejected outright.","triggerScenarios":"POSTing an extraction job with provider='openai' when the deployment only configures/permits anthropic-family providers; provider='azure-openai' vs 'openai' family mismatch; omitting provider is safe (falls back to config['llm']['provider'], always allowed).","commonSituations":"Multi-tenant deployments restricting tenants to one vendor; a provider key not set in the server env/config so its family never lands in the allowlist; renaming or adding a provider in config without updating clients.","solutions":["Check the deployment's LLM config for which providers have API keys/allow entries — allowed_provider_families() is the source of truth.","Request one of the allowed providers, or omit provider to use the configured default.","Operators: add the provider's API key / allow entry in config so its family joins the allowlist.","Catch LLMProviderNotAllowed client-side and surface an 'upgrade/choose provider' message rather than retrying."],"exampleFix":"# before\nPOST /llm/job {\"provider\": \"gemini\", ...}   # deployment allows anthropic only\n\n# after\nPOST /llm/job {\"provider\": \"anthropic\", ...}  # or omit provider for the server default","handlingStrategy":"type-guard","validationCode":"from llm_broker import allowed_provider_families, _family\n\ndef provider_allowed(config: dict, provider: str | None) -> bool:\n    fams = allowed_provider_families(config)\n    return not fams or _family(provider or config[\"llm\"][\"provider\"]) in fams","typeGuard":"def is_allowed_provider(config, requested) -> bool:\n    fams = allowed_provider_families(config)\n    return requested is None or (not fams or _family(requested) in fams)","tryCatchPattern":"from llm_broker import LLMProviderNotAllowed\n\ntry:\n    creds = resolve_llm(config, requested_provider)\nexcept LLMProviderNotAllowed:\n    creds = resolve_llm(config, None)  # fall back to server default provider","preventionTips":["Expose the allowed provider list via a config/status endpoint and drive UI dropdowns from it.","Omit provider to get the always-allowed default.","Never send base_url/api_token from clients — the server derives credentials."],"tags":["llm","authorization","config","api"],"backgroundTag":null,"analyzedSha":"7e801521428ee12509994d39151006f64055ebe3","analyzedAt":"2026-08-14T20:46:20.673Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}