BerriAI/litellm · error · GetAPIKeyError

API key response missing token

Error message

API key response missing token

What it means

Raised in get_api_key() when the Copilot API-key refresh HTTP call succeeded (status 2xx) and the response was saved, but the parsed JSON contains no 'token' field (token is None/missing). It is a 401 GetAPIKeyError, signalling that GitHub returned an unexpected 200-body from the api.github.com/copilot_internal/v2/token endpoint — typically an auth-passthrough or contract change rather than a normal expiry (expiry paths raise RefreshAPIKeyError instead).

Source

Thrown at litellm/llms/github_copilot/authenticator.py:115

                        message="API key expired",
                        status_code=401,
                    )
        except OSError:
            verbose_logger.warning("No API key file found or error opening file")
        except (json.JSONDecodeError, KeyError) as e:
            verbose_logger.warning("Error reading API key from file: %s", e)
        except APIKeyExpiredError:
            pass  # Already logged in the try block

        try:
            api_key_info = self._refresh_api_key()
            with open(self.api_key_file, "w") as f:
                json.dump(api_key_info, f)
            token: Final = api_key_info.get("token")
            if token:
                return token
            else:
                raise GetAPIKeyError(
                    message="API key response missing token",
                    status_code=401,
                )
        except OSError as e:
            verbose_logger.error("Error saving API key to file: %s", e)
            raise GetAPIKeyError(
                message=f"Failed to save API key: {e}",
                status_code=500,
            )
        except RefreshAPIKeyError as e:
            raise GetAPIKeyError(
                message=f"Failed to refresh API key: {e}",
                status_code=401,
            )

    def get_api_base(self) -> str | None:
        """
        Get the API endpoint from the api-key.json file.

View on GitHub (pinned to 6c2dcb801b)

Solutions

  1. Delete the cached token files (api-key.json and access-token file under the token dir, e.g. ~/.copilot-like dir) to force a clean re-auth, then retry.
  2. Test the refresh endpoint directly with your cached access token and inspect the JSON fields it actually returns.
  3. Disable/allowlist proxies for api.github.com — response interception commonly rewrites the body.
  4. If GitHub changed the response shape, update litellm to the latest version where the Copilot authenticator tracks the current endpoint contract.

Example fix

# before: corrupted/legacy api-key.json without "token" -> GetAPIKeyError 401 every call
import litellm
litellm.completion(model="github_copilot/gpt-4o", messages=[...])

# after: clear the stale cache to force a fresh OAuth + refresh cycle
import shutil, pathlib
cache = pathlib.Path.home() / ".litellm" / "github_copilot"  # token dir used by the authenticator
if cache.exists():
    shutil.rmtree(cache)
litellm.completion(model="github_copilot/gpt-4o", messages=[...])  # re-authenticates cleanly
Defensive patterns

Strategy: fallback

Validate before calling

import json, pathlib

cache = pathlib.Path("~/.litellm/github_copilot/api-key.json").expanduser()
if cache.exists():
    try:
        data = json.loads(cache.read_text())
    except json.JSONDecodeError:
        data = {}
    if not data.get("token"):
        cache.unlink()  # force a clean refresh instead of failing on the bad cache

Type guard

def has_valid_cached_api_key(cache_path: pathlib.Path) -> bool:
    """True if the Copilot api-key cache holds a non-empty token."""
    if not cache_path.exists():
        return False
    try:
        return bool(json.loads(cache_path.read_text()).get("token"))
    except (json.JSONDecodeError, OSError):
        return False

Try / catch

from litellm.exceptions import AuthenticationError

try:
    resp = litellm.completion(model="github_copilot/gpt-4o", messages=msgs)
except AuthenticationError as e:
    if "missing token" in str(e):
        clear_copilot_cache()  # delete api-key.json + access token files
        resp = litellm.completion(model="github_copilot/gpt-4o", messages=msgs)  # one clean retry
    else:
        raise

Prevention

When it happens

Trigger: The token refresh returns 200 with a JSON body lacking 'token' — e.g. an intercepted/rewritten response from a proxy, an unexpected GitHub response shape, or an api-key.json cache file that was hand-edited/corrupted so api_key_info parsed from cache has no token.

Common situations: Corporate proxies stripping Authorization headers so the endpoint returns an anonymous 200 JSON error body; GitHub changing the internal Copilot token endpoint response (these internal endpoints are undocumented and can shift); truncated or corrupted api-key.json from concurrent writers.

Related errors


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