BerriAI/litellm · error · Exception
dynamic_api_key needs to be a string. Got type={type(dynamic
Error message
dynamic_api_key needs to be a string. Got type={type(dynamic_api_key).__name__} What it means
Type guard alongside the api_base check: for provider-prefixed models, dynamic_api_key must be a string when provided. Passing any non-string (dict, bytes, object) raises this Exception (surfacing as BadRequestError via the wrapper).
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 6c2dcb801b)
Solutions
- Unwrap secret objects to strings before the call (e.g. secret.get_secret_value()).
- Pass api_key as a plain string literal or env-var string.
- Add a config-time assertion that api_key is a str.
Example fix
# before
litellm.completion(model='openai/m', api_key=SecretStr('sk-...'), ...)
# after
litellm.completion(model='openai/m', api_key=secret.get_secret_value(), ...) Defensive patterns
Strategy: validation
Validate before calling
def api_key_valid(api_key) -> bool:
return api_key is None or isinstance(api_key, str) Type guard
function isApiKey(v: unknown): v is string | undefined {
return v === undefined || v === null || typeof v === 'string';
} Prevention
- Unwrap SecretStr with get_secret_value() before passing.
- Decode bytes secrets to str at the config boundary.
- Never pass credential objects; pass their token string.
When it happens
Trigger: litellm.completion(model='openai/m', api_key={'key': ...}) where api_key propagates as dynamic_api_key, or config pipelines passing a secret object/SecretStr instead of its string value.
Common situations: Pydantic SecretStr not unwrapped (.get_secret_value() forgotten), config YAML nesting the key under an object, or passing an os.environ mapping instead of a value.
Related errors
- dynamic_api_key needs to be a string. dynamic_api_key={dynam
- limit must be an integer
- api base needs to be a string. api_base={api_base}
- Invalid image_url type: {type(image_url).__name__}. Expected
- max retries must be an int
AI-assisted analysis of BerriAI/litellm@6c2dcb801b (2026-08-15).
Data as JSON: /api/errors/e87f4010a66a259f.
Report an issue: GitHub.