{"record":{"id":"2fd924630369fb96","repo":"BerriAI/litellm","slug":"dynamic-api-key-needs-to-be-a-string-dynamic-api","errorCode":null,"errorMessage":"dynamic_api_key needs to be a string. dynamic_api_key={dynamic_api_key}","messagePattern":"dynamic_api_key needs to be a string\\. dynamic_api_key=(.+?)","errorType":"validation","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"litellm/litellm_core_utils/get_llm_provider_logic.py","lineNumber":356,"sourceCode":"                        custom_llm_provider = \"hyperbolic\"\n                        dynamic_api_key = get_secret_str(\"HYPERBOLIC_API_KEY\")\n                    elif endpoint == \"https://ai-gateway.vercel.sh/v1\":\n                        custom_llm_provider = \"vercel_ai_gateway\"\n                        dynamic_api_key = get_secret_str(\"VERCEL_AI_GATEWAY_API_KEY\")\n                    elif endpoint == \"https://api.inference.wandb.ai/v1\":\n                        custom_llm_provider = \"wandb\"\n                        dynamic_api_key = get_secret_str(\"WANDB_API_KEY\")\n                    elif endpoint == \"https://pinstripes.io/v1\":\n                        custom_llm_provider = \"pinstripes\"\n                        dynamic_api_key = get_secret_str(\"PINSTRIPES_API_KEY\")\n                    elif endpoint == \"https://api.meta.ai/v1\":\n                        custom_llm_provider = \"meta\"\n                        dynamic_api_key = get_secret_str(\"META_API_KEY\")\n\n                    if api_base is not None and not isinstance(api_base, str):\n                        raise Exception(f\"api base needs to be a string. api_base={api_base}\")\n                    if dynamic_api_key is not None and not isinstance(dynamic_api_key, str):\n                        raise Exception(f\"dynamic_api_key needs to be a string. dynamic_api_key={dynamic_api_key}\")\n                    return model, custom_llm_provider, dynamic_api_key, api_base\n\n        # 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.)\n        ## openai - chatcompletion + text completion\n        if (\n            model in litellm.open_ai_chat_completion_models\n            or \"ft:gpt-3.5-turbo\" in model\n            or \"ft:gpt-4\" in model  # catches ft:gpt-4-0613, ft:gpt-4o\n            or model in litellm.openai_image_generation_models\n            or model.startswith(\"gpt-image\")\n            or model in litellm.openai_video_generation_models\n        ):\n            custom_llm_provider = \"openai\"\n        elif model in litellm.open_ai_text_completion_models:\n            custom_llm_provider = \"text-completion-openai\"\n        ## anthropic\n        elif model in litellm.anthropic_models:\n            if litellm.AnthropicTextConfig._is_anthropic_text_model(model):","sourceCodeStart":338,"sourceCodeEnd":374,"githubUrl":"https://github.com/BerriAI/litellm/blob/77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8/litellm/litellm_core_utils/get_llm_provider_logic.py#L338-L374","documentation":"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.","triggerScenarios":"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.","commonSituations":"Passing SecretStr or bytes keys from settings layers; key lists intended for rotation; mostly seen in code that generically forwards credential objects.","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."],"exampleFix":"# before\nfrom pydantic import SecretStr\nlitellm.completion(model='gpt-3.5-turbo', api_base='https://api.together.ai/v1', api_key=SecretStr('sk-...'))\n\n# after\nlitellm.completion(model='gpt-3.5-turbo', api_base='https://api.together.ai/v1', api_key='sk-...')","handlingStrategy":"type-guard","validationCode":"if api_key is not None and not isinstance(api_key, str):\n    api_key = api_key.get_secret_value() if hasattr(api_key, 'get_secret_value') else str(api_key)\nresp = litellm.completion(model='gpt-3.5-turbo', api_base='https://api.together.ai/v1', api_key=api_key)","typeGuard":"def is_valid_dynamic_api_key(value: object) -> bool:\n    return value is None or isinstance(value, str)","tryCatchPattern":"try:\n    resp = litellm.completion(...)\nexcept litellm.exceptions.BadRequestError as e:\n    if 'dynamic_api_key needs to be a string' in str(e):\n        # normalize the credential and retry once\n        raise\n    raise","preventionTips":["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."],"tags":["litellm","api-key","type-validation","openai-compatible"],"backgroundTag":"invalid-argument-value","analyzedSha":"77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8","analyzedAt":"2026-08-18T11:44:31.656Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}