{"record":{"id":"3c03e99f0c942031","repo":"BerriAI/litellm","slug":"github-copilot-api-key-is-required-please-authent","errorCode":null,"errorMessage":"GitHub Copilot API key is required. Please authenticate via OAuth Device Flow.","messagePattern":"GitHub Copilot API key is required\\. Please authenticate via OAuth Device Flow\\.","errorType":"exception","errorClass":"AuthenticationError","httpStatus":401,"severity":"critical","filePath":"litellm/llms/github_copilot/embedding/transformation.py","lineNumber":66,"sourceCode":"    def validate_environment(\n        self,\n        headers: dict,\n        model: str,\n        messages: list,\n        optional_params: dict,\n        litellm_params: dict,\n        api_key: str | None = None,\n        api_base: str | None = None,\n    ) -> dict:\n        \"\"\"\n        Validate environment and set up headers for GitHub Copilot API.\n        \"\"\"\n        try:\n            # Get GitHub Copilot API key via OAuth\n            api_key = self.authenticator.get_api_key()\n\n            if not api_key:\n                raise AuthenticationError(\n                    model=model,\n                    llm_provider=\"github_copilot\",\n                    message=\"GitHub Copilot API key is required. Please authenticate via OAuth Device Flow.\",\n                )\n\n            # Get default headers\n            default_headers: Final = get_copilot_default_headers(api_key)\n\n            # Merge with existing headers (user's extra_headers take priority)\n            merged_headers: Final = {**default_headers, **headers}\n\n            verbose_logger.debug(\"GitHub Copilot Embedding API: Successfully configured headers for model %s\", model)\n\n            return merged_headers\n\n        except GetAPIKeyError as e:\n            raise AuthenticationError(\n                model=model,","sourceCodeStart":48,"sourceCodeEnd":84,"githubUrl":"https://github.com/BerriAI/litellm/blob/6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d/litellm/llms/github_copilot/embedding/transformation.py#L48-L84","documentation":"Raised in the Copilot embedding transformation's validate_environment when the OAuth-obtained API key is falsy after get_api_key() returns (None or empty string). This branch is defensive: get_api_key() normally raises on failure rather than returning empty, so hitting this means the authenticator returned an empty value without error — e.g. a hand-placed api-key.json whose 'token' is an empty string. The message directs you to the OAuth Device Flow.","triggerScenarios":"Calling litellm.embedding(..., model=\"github_copilot/...\") when the token cache contains an api-key.json with an empty/None 'token' value, or the authenticator path returns an empty key. Any real refresh/expiry failure would raise GetAPIKeyError instead (caught separately below this check).","commonSituations":"Users manually creating or editing api-key.json templates and leaving token blank; secrets managers injecting an empty string as the cached token; partial writes to the cache file from a killed process.","solutions":["Delete the corrupted api-key.json cache and let the authenticator refresh/re-authenticate from scratch.","Complete the GitHub Copilot OAuth device flow (litellm --login github_copilot, then verify at github.com/login/device).","If injecting a pre-baked token cache in deployments, validate it contains a non-empty 'token' before shipping.","Re-run the embedding call after the cache is repopulated."],"exampleFix":"# before: hand-crafted cache with empty token\n# ~/.litellm/github_copilot/api-key.json: {\"token\": \"\", \"expires_at\": 0}\nlitellm.embedding(model=\"github_copilot/text-embedding-3-small\", input=[\"hello\"])\n\n# after: remove the broken cache; authenticate via device flow so token is real\nimport pathlib, shutil\ncache = pathlib.Path(\"~/.litellm/github_copilot/api-key.json\").expanduser()\nif cache.exists(): cache.unlink()\n# litellm --login github_copilot  (one-time)\nlitellm.embedding(model=\"github_copilot/text-embedding-3-small\", input=[\"hello\"])","handlingStrategy":"validation","validationCode":"import json, pathlib\n\ncache = pathlib.Path(\"~/.litellm/github_copilot/api-key.json\").expanduser()\nif cache.exists():\n    token = json.loads(cache.read_text()).get(\"token\")\n    if not token:\n        cache.unlink()\n        print(\"removed empty-token cache; device-flow login will re-create it\")","typeGuard":"def copilot_cache_has_token(cache_path: pathlib.Path) -> bool:\n    try:\n        return bool(json.loads(cache_path.read_text()).get(\"token\"))\n    except (FileNotFoundError, json.JSONDecodeError, OSError):\n        return False","tryCatchPattern":"from litellm.exceptions import AuthenticationError\n\ntry:\n    emb = litellm.embedding(model=\"github_copilot/text-embedding-3-small\", input=[\"hi\"])\nexcept AuthenticationError as e:\n    if \"authenticate via OAuth Device Flow\" in str(e):\n        raise SystemExit(\"Run 'litellm --login github_copilot' once, then retry\") from e\n    raise","preventionTips":["Validate any deployed token cache contains a non-empty token.","Never template api-key.json by hand.","Use the device flow as the only path that writes the cache.","Add a CI check that rejects empty-string secrets bound for the Copilot cache."],"tags":["github-copilot","embedding","authentication","oauth","empty-token"],"backgroundTag":null,"analyzedSha":"6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d","analyzedAt":"2026-08-15T07:12:03.035Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}