BerriAI/litellm · error · ValueError

advisor tool definition sets 'api_base' without 'api_key'. A

Error message

advisor tool definition sets 'api_base' without 'api_key'. A caller-supplied api_base is only honored alongside a caller-supplied api_key, so the proxy's own credentials are never sent to a caller-chosen destination.

What it means

Security guard in the advisor credential resolver. A caller may point the advisor at a custom api_base only when the caller also supplies the api_key for that destination. Supplying api_base without api_key is rejected so the proxy never attaches its own credentials to a caller-chosen URL (credential-exfiltration / SSRF-to-credential-theft protection). Only active when client-side advisor credentials are allowed.

Source

Thrown at litellm/llms/anthropic/experimental_pass_through/messages/interceptors/advisor.py:214

    the caller-chosen ``api_base``. A caller-supplied ``api_base`` is also
    required to be https with TLS verification on, and SSRF-validated so it
    can't target a private/internal/cloud-metadata address, mirroring
    ``proxy.auth.auth_utils.check_complete_credentials``. https with TLS
    verification is required because ``validate_url`` only rewrites the
    connection to a DNS-pinned IP for http, or for https with
    ``litellm.ssl_verify`` disabled; otherwise it returns the URL unchanged
    and relies on certificate validation to block DNS rebinding, so this
    closes the same gap without threading the pinned URL through the whole
    ``anthropic_messages()`` call chain.
    """
    if not _allow_client_side_advisor_credentials():
        return None, None
    api_key: Final[str | None] = advisor_tool.get("api_key")
    api_base: Final[str | None] = advisor_tool.get("api_base")
    if api_base is None:
        return api_key, None
    if not api_key:
        raise ValueError(
            "advisor tool definition sets 'api_base' without 'api_key'. A "
            "caller-supplied api_base is only honored alongside a "
            "caller-supplied api_key, so the proxy's own credentials are "
            "never sent to a caller-chosen destination."
        )
    if not api_base.startswith("https://"):
        raise ValueError(f"advisor tool definition sets 'api_base'={api_base!r}, which must use the https scheme.")
    if getattr(litellm, "ssl_verify", True) is False:
        raise ValueError(
            "advisor tool definition sets 'api_base' but the proxy has TLS verification "
            "disabled (litellm.ssl_verify=False), so a caller-supplied api_base can't be "
            "safely validated against DNS rebinding."
        )
    if getattr(litellm, "user_url_validation", True):
        validate_url(api_base)
    return api_key, api_base

View on GitHub (pinned to 6c2dcb801b)

Solutions

  1. Supply both fields together: api_key and api_base on the advisor tool definition.
  2. If the destination should use the gateway's configured credentials, remove 'api_base' and configure that model in the proxy's model_list instead.
  3. If you truly need gateway credentials at a custom base, register that model+credentials server-side rather than client-side.

Example fix

# before
tools = [{"type": "advisor", "model": "claude-haiku-4-5", "api_base": "https://internal.example.com"}]

# after
tools = [{
    "type": "advisor",
    "model": "claude-haiku-4-5",
    "api_base": "https://internal.example.com",
    "api_key": "sk-internal-key",
}]
Defensive patterns

Strategy: validation

Validate before calling

def validate_advisor_credentials(advisor_tool: dict) -> None:
    api_base = advisor_tool.get("api_base")
    api_key = advisor_tool.get("api_key")
    if api_base is not None and not api_key:
        raise ValueError("advisor api_base requires a matching caller-supplied api_key")

Type guard

def advisor_credentials_are_paired(tool: dict) -> bool:
    api_base = tool.get("api_base")
    api_key = tool.get("api_key")
    return api_base is None or bool(api_key)

Try / catch

try:
    resp = litellm.anthropic_messages(tools=tools, ...)
except ValueError as e:
    if "api_base" in str(e) and "api_key" in str(e):
        log_security_event("advisor_credential_mismatch", str(e))
        return http_error(400, "advisor api_base must be paired with api_key")
    raise

Prevention

When it happens

Trigger: Sending an advisor tool with {"type": "advisor", "model": ..., "api_base": "https://my-llm.example.com"} but no 'api_key' key (or api_key=None/""). The resolver sees api_base set, api_key falsy, and raises.

Common situations: Teams pointing the advisor at an internal vLLM endpoint but expecting the proxy's key to be reused; half-finished config where the base was migrated to the tool definition but the key was left in an env var; attempts (accidental or malicious) to make the gateway forward its Anthropic key to an attacker-controlled host.

Related errors


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