{"record":{"id":"e87f4010a66a259f","repo":"BerriAI/litellm","slug":"dynamic-api-key-needs-to-be-a-string-got-type-ty","errorCode":null,"errorMessage":"dynamic_api_key needs to be a string. Got type={type(dynamic_api_key).__name__}","messagePattern":"dynamic_api_key needs to be a string\\. Got type=(.+?)","errorType":"validation","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"litellm/litellm_core_utils/get_llm_provider_logic.py","lineNumber":227,"sourceCode":"            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\"\n                        dynamic_api_key = get_secret_str(\"MISTRAL_API_KEY\")\n                    elif endpoint == \"api.groq.com/openai/v1\":","sourceCodeStart":209,"sourceCodeEnd":245,"githubUrl":"https://github.com/BerriAI/litellm/blob/77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8/litellm/litellm_core_utils/get_llm_provider_logic.py#L209-L245","documentation":"Raised by get_llm_provider() when a provider-prefixed model resolves successfully but the resolved dynamic_api_key is not None and not a str. dynamic_api_key usually carries the api_key you passed to completion(); LiteLLM rejects lists, bytes, SecretStr, and other objects here because provider configs interpolate the key directly into Authorization headers. The raise is wrapped into a litellm.exceptions.BadRequestError ('GetLLMProvider Exception - ...') by the function's except block.","triggerScenarios":"litellm.completion(model='groq/...', api_key=['sk-1','sk-2']) or api_key=os.getenv('KEY').encode() / SecretStr('sk-...') / a dict, where the model prefix is in litellm.provider_list. List api_key rotation is only supported on the OpenAI-compatible path, so the same call with 'openai/...' works but 'groq/...' raises.","commonSituations":"Porting key-rotation setups from openai models to other providers; passing pydantic SecretStr values straight from FastAPI/Pydantic settings objects; YAML list syntax for api_key in config.yaml.","solutions":["Pass a single string key: api_key='sk-...' or api_key=os.getenv('GROQ_API_KEY').","Unwrap typed secrets first: api_key=settings.groq_key.get_secret_value().","For multiple keys, use litellm.Router with several deployments of the same model and let the router rotate.","Set the provider's documented env var (e.g. GROQ_API_KEY) and omit api_key entirely."],"exampleFix":"# before\nlitellm.completion(model='groq/llama-3.1-8b-instant', api_key=[key1, key2])\n\n# after\nlitellm.completion(model='groq/llama-3.1-8b-instant', api_key=key1)","handlingStrategy":"type-guard","validationCode":"import os\n\napi_key = os.getenv('GROQ_API_KEY')\nassert api_key is None or isinstance(api_key, str), f'api_key must be a str, got {type(api_key).__name__}'\nresp = litellm.completion(model='groq/llama-3.1-8b-instant', api_key=api_key)","typeGuard":"def is_valid_api_key(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_key=api_key)\nexcept litellm.exceptions.BadRequestError as e:\n    if 'dynamic_api_key needs to be a string' in str(e):\n        api_key = api_key.get_secret_value() if hasattr(api_key, 'get_secret_value') else str(api_key)\n        resp = litellm.completion(model='groq/llama-3.1-8b-instant', api_key=api_key)\n    else:\n        raise","preventionTips":["Prefer provider env vars (GROQ_API_KEY etc.) over passing api_key.","Centralize a normalize_key() helper: SecretStr -> .get_secret_value(), bytes -> decode().","Do key rotation with Router deployments, never with list api_key on non-openai providers."],"tags":["litellm","api-key","type-validation","provider-routing","secret-handling"],"backgroundTag":"invalid-argument-value","analyzedSha":"77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8","analyzedAt":"2026-08-18T11:44:31.656Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}