BerriAI/litellm · error · Exception
dynamic_api_key needs to be a string. dynamic_api_key=
Error message
dynamic_api_key needs to be a string. dynamic_api_key={dynamic_api_key} What it means
Same endpoint-matching branch of get_llm_provider() as the api_base check, but for dynamic_api_key: after a known OpenAI-compatible endpoint sets defaults via get_secret_str(), the value must be a str. get_secret_str() always returns str|None, so in practice this fires only when you explicitly passed a non-str api_key/dynamic_api_key alongside an api_base that matched a known endpoint. Wrapped into BadRequestError by the outer except.
Solutions
- Pass the key as a plain string.
- Unwrap SecretStr with .get_secret_value() and decode bytes before calling.
- Prefer the provider env var (e.g. TOGETHERAI_API_KEY) and omit api_key.
Example fix
# before
from pydantic import SecretStr
litellm.completion(model='gpt-3.5-turbo', api_base='https://api.together.ai/v1', api_key=SecretStr('sk-...'))
# after
litellm.completion(model='gpt-3.5-turbo', api_base='https://api.together.ai/v1', api_key='sk-...') Defensive patterns
Strategy: type-guard
Validate before calling
if api_key is not None and not isinstance(api_key, str):
api_key = api_key.get_secret_value() if hasattr(api_key, 'get_secret_value') else str(api_key)
resp = litellm.completion(model='gpt-3.5-turbo', api_base='https://api.together.ai/v1', api_key=api_key) Type guard
def is_valid_dynamic_api_key(value: object) -> bool:
return value is None or isinstance(value, str) Try / catch
try:
resp = litellm.completion(...)
except litellm.exceptions.BadRequestError as e:
if 'dynamic_api_key needs to be a string' in str(e):
# normalize the credential and retry once
raise
raise Prevention
- Unwrap SecretStr/bytes credentials once, in a settings loader.
- Let the provider env var (e.g. TOGETHERAI_API_KEY) supply the key instead of passing objects.
When it happens
Trigger: completion(..., api_base='https://api.together.ai/v1', api_key=os.environb[b'KEY']) or a SecretStr/list api_key where the endpoint match selected provider defaults and then the type guard rejects your key value.
Common situations: Passing SecretStr or bytes keys from settings layers; key lists intended for rotation; mostly seen in code that generically forwards credential objects.
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
- dynamic_api_key needs to be a string. Got type=
- api base needs to be a string. api_base=
- 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/2fd924630369fb96.
Report an issue: GitHub.
Appendix: source
Thrown at litellm/litellm_core_utils/get_llm_provider_logic.py:356
custom_llm_provider = "hyperbolic"
dynamic_api_key = get_secret_str("HYPERBOLIC_API_KEY")
elif endpoint == "https://ai-gateway.vercel.sh/v1":
custom_llm_provider = "vercel_ai_gateway"
dynamic_api_key = get_secret_str("VERCEL_AI_GATEWAY_API_KEY")
elif endpoint == "https://api.inference.wandb.ai/v1":
custom_llm_provider = "wandb"
dynamic_api_key = get_secret_str("WANDB_API_KEY")
elif endpoint == "https://pinstripes.io/v1":
custom_llm_provider = "pinstripes"
dynamic_api_key = get_secret_str("PINSTRIPES_API_KEY")
elif endpoint == "https://api.meta.ai/v1":
custom_llm_provider = "meta"
dynamic_api_key = get_secret_str("META_API_KEY")
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. dynamic_api_key={dynamic_api_key}")
return model, custom_llm_provider, dynamic_api_key, api_base
# check if model in known model provider list -> for huggingface models, raise exception as they don't have a fixed provider (can be togetherai, anyscale, baseten, runpod, et.)
## openai - chatcompletion + text completion
if (
model in litellm.open_ai_chat_completion_models
or "ft:gpt-3.5-turbo" in model
or "ft:gpt-4" in model # catches ft:gpt-4-0613, ft:gpt-4o
or model in litellm.openai_image_generation_models
or model.startswith("gpt-image")
or model in litellm.openai_video_generation_models
):
custom_llm_provider = "openai"
elif model in litellm.open_ai_text_completion_models:
custom_llm_provider = "text-completion-openai"
## anthropic
elif model in litellm.anthropic_models:
if litellm.AnthropicTextConfig._is_anthropic_text_model(model):View on GitHub (pinned to 77b7c6c40c)