BerriAI/litellm · error · Exception
dynamic_api_key needs to be a string. Got type=
Error message
dynamic_api_key needs to be a string. Got type={type(dynamic_api_key).__name__} What it means
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.
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.
Example fix
# before litellm.completion(model='groq/llama-3.1-8b-instant', api_key=[key1, key2]) # after litellm.completion(model='groq/llama-3.1-8b-instant', api_key=key1)
Defensive patterns
Strategy: type-guard
Validate before calling
import os
api_key = os.getenv('GROQ_API_KEY')
assert api_key is None or isinstance(api_key, str), f'api_key must be a str, got {type(api_key).__name__}'
resp = litellm.completion(model='groq/llama-3.1-8b-instant', api_key=api_key) Type guard
def is_valid_api_key(value: object) -> bool:
return value is None or isinstance(value, str) Try / catch
try:
resp = litellm.completion(model='groq/llama-3.1-8b-instant', api_key=api_key)
except litellm.exceptions.BadRequestError as e:
if 'dynamic_api_key needs to be a string' in str(e):
api_key = api_key.get_secret_value() if hasattr(api_key, 'get_secret_value') else str(api_key)
resp = litellm.completion(model='groq/llama-3.1-8b-instant', api_key=api_key)
else:
raise Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- api base needs to be a string. api_base=
- dynamic_api_key needs to be a string. dynamic_api_key=
- API key is required
- API key is required for OpenAI image variations
- API key is required for Topaz image variations
AI-assisted analysis of BerriAI/litellm@77b7c6c40c (2026-08-18).
Data as JSON: /api/errors/e87f4010a66a259f.
Report an issue: GitHub.
Appendix: source
Thrown at litellm/litellm_core_utils/get_llm_provider_logic.py:227
model.split("/", 1)[0] in litellm.provider_list
and model.split("/", 1)[0] not in litellm.model_list_set
and len(model.split("/"))
> 1 # handle edge case where user passes in `litellm --model mistral` https://github.com/BerriAI/litellm/issues/1351
):
return _get_openai_compatible_provider_info(
model=model,
api_base=api_base,
api_key=api_key,
dynamic_api_key=dynamic_api_key,
litellm_params=litellm_params,
)
elif model.split("/", 1)[0] in litellm.provider_list:
custom_llm_provider = model.split("/", 1)[0]
model = model.split("/", 1)[1]
if api_base is not None and not isinstance(api_base, str):
raise Exception(f"api base needs to be a string. api_base={api_base}")
if dynamic_api_key is not None and not isinstance(dynamic_api_key, str):
raise Exception(f"dynamic_api_key needs to be a string. Got type={type(dynamic_api_key).__name__}")
return model, custom_llm_provider, dynamic_api_key, api_base
# check if api base is a known openai compatible endpoint
if api_base:
for endpoint in litellm.openai_compatible_endpoints:
if _endpoint_matches_api_base(endpoint, api_base):
if endpoint == "api.perplexity.ai":
custom_llm_provider = "perplexity"
dynamic_api_key = get_secret_str("PERPLEXITYAI_API_KEY")
elif endpoint == "api.endpoints.anyscale.com/v1":
custom_llm_provider = "anyscale"
dynamic_api_key = get_secret_str("ANYSCALE_API_KEY")
elif endpoint == "api.deepinfra.com/v1/openai":
custom_llm_provider = "deepinfra"
dynamic_api_key = get_secret_str("DEEPINFRA_API_KEY")
elif endpoint == "api.mistral.ai/v1":
custom_llm_provider = "mistral"
dynamic_api_key = get_secret_str("MISTRAL_API_KEY")
elif endpoint == "api.groq.com/openai/v1":View on GitHub (pinned to 77b7c6c40c)