{"record":{"id":"d5f945e0dcdfe485","repo":"BerriAI/litellm","slug":"api-base-needs-to-be-a-string-api-base-api-base","errorCode":null,"errorMessage":"api base needs to be a string. api_base={api_base}","messagePattern":"api base needs to be a string\\. api_base=(.+?)","errorType":"validation","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"litellm/litellm_core_utils/get_llm_provider_logic.py","lineNumber":225,"sourceCode":"\n        if (\n            model.split(\"/\", 1)[0] in litellm.provider_list\n            and model.split(\"/\", 1)[0] not in litellm.model_list_set\n            and len(model.split(\"/\"))\n            > 1  # handle edge case where user passes in `litellm --model mistral` https://github.com/BerriAI/litellm/issues/1351\n        ):\n            return _get_openai_compatible_provider_info(\n                model=model,\n                api_base=api_base,\n                api_key=api_key,\n                dynamic_api_key=dynamic_api_key,\n                litellm_params=litellm_params,\n            )\n        elif model.split(\"/\", 1)[0] in litellm.provider_list:\n            custom_llm_provider = model.split(\"/\", 1)[0]\n            model = model.split(\"/\", 1)[1]\n            if api_base is not None and not isinstance(api_base, str):\n                raise Exception(f\"api base needs to be a string. api_base={api_base}\")\n            if dynamic_api_key is not None and not isinstance(dynamic_api_key, str):\n                raise Exception(f\"dynamic_api_key needs to be a string. Got type={type(dynamic_api_key).__name__}\")\n            return model, custom_llm_provider, dynamic_api_key, api_base\n        # check if api base is a known openai compatible endpoint\n        if api_base:\n            for endpoint in litellm.openai_compatible_endpoints:\n                if _endpoint_matches_api_base(endpoint, api_base):\n                    if endpoint == \"api.perplexity.ai\":\n                        custom_llm_provider = \"perplexity\"\n                        dynamic_api_key = get_secret_str(\"PERPLEXITYAI_API_KEY\")\n                    elif endpoint == \"api.endpoints.anyscale.com/v1\":\n                        custom_llm_provider = \"anyscale\"\n                        dynamic_api_key = get_secret_str(\"ANYSCALE_API_KEY\")\n                    elif endpoint == \"api.deepinfra.com/v1/openai\":\n                        custom_llm_provider = \"deepinfra\"\n                        dynamic_api_key = get_secret_str(\"DEEPINFRA_API_KEY\")\n                    elif endpoint == \"api.mistral.ai/v1\":\n                        custom_llm_provider = \"mistral\"","sourceCodeStart":207,"sourceCodeEnd":243,"githubUrl":"https://github.com/BerriAI/litellm/blob/77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8/litellm/litellm_core_utils/get_llm_provider_logic.py#L207-L243","documentation":"Raised inside get_llm_provider() when the model string starts with a known provider prefix (e.g. 'groq/...', 'vertex_ai/...') and the supplied api_base is not None and not a Python str. LiteLLM type-checks api_base here because every downstream provider handler concatenates it into request URLs, so a list or URL object would fail obscurely later. The raw Exception is caught by get_llm_provider's except block and re-raised as litellm.exceptions.BadRequestError prefixed with 'GetLLMProvider Exception - ...'.","triggerScenarios":"Calling litellm.completion()/acompletion() with model='<provider>/<model>' where the prefix is in litellm.provider_list, plus api_base as a list (['https://a','https://b']), an httpx.URL object, a pydantic SecretStr, a dict, or bytes. Only fires when no earlier OpenAI-compatible branch (which handles list api_base) matched.","commonSituations":"Copying the multi-base-URL load-balancing pattern (api_base as a YAML list) to a provider that is not routed through the OpenAI-compatible branch; passing httpx.URL or SecretStr objects from application settings; YAML indentation errors that make api_base a mapping instead of a scalar.","solutions":["Pass api_base as a plain string URL, e.g. api_base='https://api.groq.com/openai/v1'.","If you need multiple base URLs, use 'openai/<model>' with an api_base list (the supported form) or define separate Router deployments, one api_base each.","Coerce non-str objects before the call: str(httpx_url) or secret_str.get_secret_value().","Check config.yaml indentation so litellm_params.api_base is a scalar string, not a list or nested map."],"exampleFix":"# before\nlitellm.completion(model='groq/llama-3.1-8b-instant', api_base=['https://api.groq.com/openai/v1'])\n\n# after\nlitellm.completion(model='groq/llama-3.1-8b-instant', api_base='https://api.groq.com/openai/v1')","handlingStrategy":"type-guard","validationCode":"import litellm\n\napi_base = 'https://api.groq.com/openai/v1'\nassert api_base is None or isinstance(api_base, str), f'api_base must be a str, got {type(api_base).__name__}'\nresp = litellm.completion(model='groq/llama-3.1-8b-instant', api_base=api_base, messages=[{'role': 'user', 'content': 'hi'}])","typeGuard":"def is_valid_api_base(value: object) -> bool:\n    return value is None or isinstance(value, str)","tryCatchPattern":"try:\n    resp = litellm.completion(model='groq/llama-3.1-8b-instant', api_base=api_base)\nexcept litellm.exceptions.BadRequestError as e:\n    if 'api base needs to be a string' in str(e):\n        resp = litellm.completion(model='groq/llama-3.1-8b-instant', api_base=str(api_base))\n    else:\n        raise","preventionTips":["Keep a single str constant for each provider's base URL and reuse it.","At app startup, assert isinstance on every api_base you forward to litellm.","Reserve list api_base for 'openai/'-prefixed models only.","str() httpx.URL / unwrap SecretStr at your config boundary, not at call sites."],"tags":["litellm","api-base","type-validation","provider-routing","configuration"],"backgroundTag":"invalid-argument-value","analyzedSha":"77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8","analyzedAt":"2026-08-18T11:44:31.656Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}