BerriAI/litellm · error · ValueError

MAVVRIK_API_ENDPOINT host must be a Mavvrik domain (e.g. htt

Error message

MAVVRIK_API_ENDPOINT host must be a Mavvrik domain (e.g. https://api.mavvrik.dev/<tenant_id>)

What it means

The second Mavvrik endpoint check parses the https URL's hostname and requires it to end with one of the allowed suffixes: .mavvrik.dev, .mavvrik.ai, or .mavvrik.app. This is an SSRF/typo guard ensuring the API key is only sent to genuine Mavvrik domains.

Source

Thrown at litellm/integrations/focus/destinations/mavvrik_destination.py:36

    get_async_httpx_client,
    httpxSpecialProvider,
)

from .base import FocusDestination, FocusTimeWindow

_MAVVRIK_ALLOWED_SUFFIXES: Final = (".mavvrik.dev", ".mavvrik.ai", ".mavvrik.app")

# GCS requires intermediate chunks to be a multiple of 256 KB.
# 8 MB gives a good balance between round-trips and memory pressure.
_GCS_CHUNK_SIZE: Final = 8 * 1024 * 1024  # 8 MB


def _validate_api_endpoint(api_endpoint: str) -> None:
    if not api_endpoint.startswith("https://"):
        raise ValueError("MAVVRIK_API_ENDPOINT must be an HTTPS URL")
    hostname: Final = (urlparse(api_endpoint).hostname or "").lower()
    if not any(hostname.endswith(suffix) for suffix in _MAVVRIK_ALLOWED_SUFFIXES):
        raise ValueError(
            "MAVVRIK_API_ENDPOINT host must be a Mavvrik domain (e.g. https://api.mavvrik.dev/<tenant_id>)"
        )


def _validate_gcs_url(url: str, label: str) -> None:
    parsed: Final = urlparse(url)
    if parsed.scheme != "https":
        raise ValueError(f"Mavvrik FOCUS destination: {label} must be HTTPS, got scheme '{parsed.scheme}'")
    hostname: Final = (parsed.hostname or "").lower()
    if not (hostname == "storage.googleapis.com" or hostname.endswith(".storage.googleapis.com")):
        raise ValueError(
            f"Mavvrik FOCUS destination: {label} must be a GCS endpoint (storage.googleapis.com), got '{hostname}'"
        )


class FocusMavvrikDestination(FocusDestination):
    """Upload FOCUS CSV exports to Mavvrik via GCS signed URL."""

View on GitHub (pinned to 6c2dcb801b)

Solutions

  1. Use an official Mavvrik domain: https://api.mavvrik.dev/<tenant_id> (or .mavvrik.ai / .mavvrik.app).
  2. Verify the exact tenant URL from the Mavvrik dashboard's connection instructions.
  3. For mocking in tests, monkeypatch FocusMavvrikDestination or the allowed-suffixes tuple rather than the endpoint value.

Example fix

# before
MAVVRIK_API_ENDPOINT=https://metrics.internal.acme.com/tenant

# after
MAVVRIK_API_ENDPOINT=https://api.mavvrik.dev/acme-tenant
Defensive patterns

Strategy: validation

Validate before calling

from urllib.parse import urlparse

allowed = (".mavvrik.dev", ".mavvrik.ai", ".mavvrik.app")
host = (urlparse(endpoint).hostname or "").lower()
if not any(host.endswith(s) for s in allowed):
    raise SystemExit(f"MAVVRIK_API_ENDPOINT host '{host}' is not a Mavvrik domain")

Type guard

from urllib.parse import urlparse

def is_mavvrik_domain(url: str) -> bool:
    host = (urlparse(url).hostname or "").lower()
    return any(host.endswith(s) for s in (".mavvrik.dev", ".mavvrik.ai", ".mavvrik.app"))

Try / catch

try:
    dest = FocusMavvrikDestination(prefix=p, config=cfg)
except ValueError as e:
    if "Mavvrik domain" in str(e):
        raise ConfigError(f"Endpoint '{endpoint}' is not an official Mavvrik host") from e
    raise

Prevention

When it happens

Trigger: Passing a valid https URL whose host is not a Mavvrik domain, e.g. https://api.mavvrik.example.com, https://mavvrik-dev.com/tenant, https://localhost:8080, or https://evil.dev (note: 'evil.dev' fails because suffix matching requires '.mavvrik.dev', not just '.dev').

Common situations: Custom/private Mavvrik deployments on a company domain; typos in the domain (mavvrik vs mavrik); trying to point the destination at a mock/staging server you control.

Related errors


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