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

  1. Pass the key as a plain string.
  2. Unwrap SecretStr with .get_secret_value() and decode bytes before calling.
  3. 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

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


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)