{"record":{"id":"081c0ea1ccd82bd3","repo":"BerriAI/litellm","slug":"no-braintrust-api-token-provided-pass-via-authori","errorCode":null,"errorMessage":"No Braintrust API token provided. Pass via Authorization header or set BRAINTRUST_API_KEY environment variable.","messagePattern":"No Braintrust API token provided\\. Pass via Authorization header or set BRAINTRUST_API_KEY environment variable\\.","errorType":"http","errorClass":"HTTPException","httpStatus":401,"severity":"error","filePath":"cookbook/litellm_proxy_server/braintrust_prompt_wrapper_server.py","lineNumber":190,"sourceCode":"    \"\"\"\n    Fetch a prompt from Braintrust and transform it to LiteLLM format.\n\n    Args:\n        prompt_id: The Braintrust prompt ID\n        authorization: Bearer token for Braintrust API (from header)\n\n    Returns:\n        JSONResponse with the transformed prompt data\n    \"\"\"\n    # Extract token from Authorization header or environment\n    braintrust_token = None\n    if authorization and authorization.startswith(\"Bearer \"):\n        braintrust_token = authorization.replace(\"Bearer \", \"\")\n    else:\n        braintrust_token = os.getenv(\"BRAINTRUST_API_KEY\")\n\n    if not braintrust_token:\n        raise HTTPException(\n            status_code=401,\n            detail=\"No Braintrust API token provided. Pass via Authorization header or set BRAINTRUST_API_KEY environment variable.\",\n        )\n\n    # Call Braintrust API\n    braintrust_url = f\"https://api.braintrust.dev/v1/prompt/{prompt_id}\"\n    headers = {\n        \"Authorization\": f\"Bearer {braintrust_token}\",\n        \"Accept\": \"application/json\",\n    }\n    print(f\"headers: {headers}\")\n    print(f\"braintrust_url: {braintrust_url}\")\n    print(f\"braintrust_token: {braintrust_token}\")\n\n    try:\n        async with httpx.AsyncClient(timeout=30.0) as client:\n            response = await client.get(braintrust_url, headers=headers)\n            response.raise_for_status()","sourceCodeStart":172,"sourceCodeEnd":208,"githubUrl":"https://github.com/BerriAI/litellm/blob/6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d/cookbook/litellm_proxy_server/braintrust_prompt_wrapper_server.py#L172-L208","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Make the custom config's transform_request_image_variation return a non-empty \"data\" payload, e.g. {\"data\": {\"image\": image, **optional_params}}.","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.","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.","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."],"exampleFix":"# before (custom config returns no \"data\")\nclass MyConfig(OpenAIImageVariationConfig):\n    def transform_request_image_variation(self, model, image, optional_params, headers):\n        return {\"image\": image, **optional_params}  # no \"data\" key -> ValueError\n\n# after\nclass MyConfig(OpenAIImageVariationConfig):\n    def transform_request_image_variation(self, model, image, optional_params, headers):\n        return {\"data\": {\"image\": image, **optional_params}}","handlingStrategy":"try-catch","validationCode":"from litellm.types.utils import LlmProviders\nfrom litellm.utils import ProviderConfigManager\n\nconfig = ProviderConfigManager.get_provider_image_variation_config(\n    model=\"dall-e-2\", provider=LlmProviders.OPENAI\n)\nassert config is not None, \"no image-variation config for openai\"\n\nfields = config.transform_request_image_variation(\n    model=\"dall-e-2\",\n    image=open(\"cat.png\", \"rb\"),\n    optional_params={\"n\": 1, \"size\": \"1024x1024\"},\n    headers={},\n)\nif not fields.get(\"data\"):\n    raise RuntimeError(\n        f\"transform returned no 'data' payload; got keys={list(fields)} — fix the config\"\n    )","typeGuard":"from typing import Any\n\ndef has_variation_request_data(fields: Any) -> bool:\n    \"\"\"True if transform_request_image_variation returned a usable 'data' payload.\"\"\"\n    return isinstance(fields, dict) and isinstance(fields.get(\"data\"), dict) and len(fields[\"data\"]) > 0","tryCatchPattern":"from litellm.llms.openai.common_utils import OpenAIError\n\ntry:\n    resp = litellm.image_variation(model=\"dall-e-2\", image=img)\nexcept (OpenAIError, ValueError) as e:\n    msg = str(e)\n    if \"data field is required\" in msg:\n        # internal transform-shape mismatch: retrying cannot help; inspect/fix the\n        # active ImageVariationConfig (custom monkeypatch or version drift)\n        raise RuntimeError(\n            \"image-variation request transform returned no 'data'; \"\n            \"check custom ImageVariationConfig or litellm version\"\n        ) from e\n    raise","preventionTips":["When subclassing an image-variation config, always return {\"data\": {...}} with at least the image argument inside; add a unit test asserting fields[\"data\"] is non-empty.","Keep multipart providers (topaz) on their own routing path; their transforms return httpx files/data fields that the OpenAI create_variation handler will reject.","Run transform_request_image_variation once in a smoke test at startup so shape mismatches fail before production traffic.","Install litellm as a single pinned version (pip install litellm==X.Y.Z) — this error is a classic symptom of a handler/config version split."],"tags":["litellm","image-variations","request-transform","openai","custom-config"],"backgroundTag":null,"analyzedSha":"6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d","analyzedAt":"2026-08-15T07:12:03.035Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}