BerriAI/litellm · error · HTTPException

No Braintrust API token provided. Pass via Authorization hea

Error message

No Braintrust API token provided. Pass via Authorization header or set BRAINTRUST_API_KEY environment variable.

What it means

After resolving the provider config, the handler calls transform_request_image_variation (handler.py:141) and expects the returned mapping to contain a non-empty "data" dict, which is then spread as client.images.create_variation(**json_data). The stock OpenAI config always returns {"data": {"image": image, **optional_params}} (transformation.py:36-41), so this ValueError means the transform produced no usable "data" payload — i.e. a custom, mismatched, or wrong-shape config object was used. Like its sibling error, it is re-raised wrapped in an OpenAIError(status_code=500) by the outer except.

Source

Thrown at cookbook/litellm_proxy_server/braintrust_prompt_wrapper_server.py:190

    """
    Fetch a prompt from Braintrust and transform it to LiteLLM format.

    Args:
        prompt_id: The Braintrust prompt ID
        authorization: Bearer token for Braintrust API (from header)

    Returns:
        JSONResponse with the transformed prompt data
    """
    # Extract token from Authorization header or environment
    braintrust_token = None
    if authorization and authorization.startswith("Bearer "):
        braintrust_token = authorization.replace("Bearer ", "")
    else:
        braintrust_token = os.getenv("BRAINTRUST_API_KEY")

    if not braintrust_token:
        raise HTTPException(
            status_code=401,
            detail="No Braintrust API token provided. Pass via Authorization header or set BRAINTRUST_API_KEY environment variable.",
        )

    # Call Braintrust API
    braintrust_url = f"https://api.braintrust.dev/v1/prompt/{prompt_id}"
    headers = {
        "Authorization": f"Bearer {braintrust_token}",
        "Accept": "application/json",
    }
    print(f"headers: {headers}")
    print(f"braintrust_url: {braintrust_url}")
    print(f"braintrust_token: {braintrust_token}")

    try:
        async with httpx.AsyncClient(timeout=30.0) as client:
            response = await client.get(braintrust_url, headers=headers)
            response.raise_for_status()

View on GitHub (pinned to 6c2dcb801b)

Solutions

  1. Make the custom config's transform_request_image_variation return a non-empty "data" payload, e.g. {"data": {"image": image, **optional_params}}.
  2. If using the stock OpenAI path, remove any monkeypatch of litellm.OpenAIImageVariationConfig so the built-in transform (which always includes the image under "data") is used.
  3. Pin or upgrade litellm to one consistent version so the handler's expectation of the "data" key matches the installed config implementation: pip install -U litellm.
  4. Route multipart/providers like Topaz through their own handler (model="topaz/...") instead of the OpenAI images.create_variation path, since their transforms return files/data httpx fields, not an OpenAI kwargs dict.

Example fix

# before (custom config returns no "data")
class MyConfig(OpenAIImageVariationConfig):
    def transform_request_image_variation(self, model, image, optional_params, headers):
        return {"image": image, **optional_params}  # no "data" key -> ValueError

# after
class MyConfig(OpenAIImageVariationConfig):
    def transform_request_image_variation(self, model, image, optional_params, headers):
        return {"data": {"image": image, **optional_params}}
Defensive patterns

Strategy: try-catch

Validate before calling

from litellm.types.utils import LlmProviders
from litellm.utils import ProviderConfigManager

config = ProviderConfigManager.get_provider_image_variation_config(
    model="dall-e-2", provider=LlmProviders.OPENAI
)
assert config is not None, "no image-variation config for openai"

fields = config.transform_request_image_variation(
    model="dall-e-2",
    image=open("cat.png", "rb"),
    optional_params={"n": 1, "size": "1024x1024"},
    headers={},
)
if not fields.get("data"):
    raise RuntimeError(
        f"transform returned no 'data' payload; got keys={list(fields)} — fix the config"
    )

Type guard

from typing import Any

def has_variation_request_data(fields: Any) -> bool:
    """True if transform_request_image_variation returned a usable 'data' payload."""
    return isinstance(fields, dict) and isinstance(fields.get("data"), dict) and len(fields["data"]) > 0

Try / catch

from litellm.llms.openai.common_utils import OpenAIError

try:
    resp = litellm.image_variation(model="dall-e-2", image=img)
except (OpenAIError, ValueError) as e:
    msg = str(e)
    if "data field is required" in msg:
        # internal transform-shape mismatch: retrying cannot help; inspect/fix the
        # active ImageVariationConfig (custom monkeypatch or version drift)
        raise RuntimeError(
            "image-variation request transform returned no 'data'; "
            "check custom ImageVariationConfig or litellm version"
        ) from e
    raise

Prevention

When it happens

Trigger: A monkeypatched/replaced litellm.OpenAIImageVariationConfig whose transform_request_image_variation returns a mapping without a "data" key or with an empty one — e.g. a Topaz-style config returning HttpHandlerRequestFields(files={"image": ...}, data=optional_params) when optional_params is empty (data={} is falsy); mixing litellm versions where the config return shape (files/data fields) no longer matches what this handler unpacks.

Common situations: Custom image-variation provider configs built by subclassing BaseImageVariationConfig but forgetting to populate the "data" field; copying the Topaz multipart transform into an OpenAI-routed call; partial upgrades where a newer config class is loaded by an older handler.

Related errors


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