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

  1. Unwrap secret objects to strings before the call (e.g. secret.get_secret_value()).
  2. Pass api_key as a plain string literal or env-var string.
  3. 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

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


AI-assisted analysis of BerriAI/litellm@6c2dcb801b (2026-08-15). Data as JSON: /api/errors/e87f4010a66a259f. Report an issue: GitHub.