infiniflow/ragflow · error · ValueError

Failed to exchange authorization code for token: {e}

Error message

Failed to exchange authorization code for token: {e}

What it means

OAuthClient.exchange_code_for_token POSTs grant_type=authorization_code with client_id, client_secret, code, redirect_uri to token_url and wraps any failure in ValueError('Failed to exchange authorization code for token: {e}') (api/apps/auth/oauth.py:79). The original exception text is preserved, so the message usually embeds the provider's HTTP status or transport error.

Source

Thrown at api/apps/auth/oauth.py:79

        return authorization_url

    def exchange_code_for_token(self, code):
        """
        Exchange authorization code for access token.
        """
        try:
            payload = {"client_id": self.client_id, "client_secret": self.client_secret, "code": code, "redirect_uri": self.redirect_uri, "grant_type": "authorization_code"}
            response = sync_request(
                "POST",
                self.token_url,
                data=payload,
                headers={"Accept": "application/json"},
                timeout=self.http_request_timeout,
            )
            response.raise_for_status()
            return response.json()
        except Exception as e:
            raise ValueError(f"Failed to exchange authorization code for token: {e}")

    async def async_exchange_code_for_token(self, code):
        """
        Async variant of exchange_code_for_token using httpx.
        """
        payload = {
            "client_id": self.client_id,
            "client_secret": self.client_secret,
            "code": code,
            "redirect_uri": self.redirect_uri,
            "grant_type": "authorization_code",
        }
        try:
            response = await async_request(
                "POST",
                self.token_url,
                data=payload,
                headers={"Accept": "application/json"},

View on GitHub (pinned to 554fb1133a)

Solutions

  1. Compare the redirect_uri sent in the token request with the one in the initial authorization request and the provider's registered callback - they must match exactly.
  2. Verify client_id/client_secret are current and copied without whitespace.
  3. Ensure the code is used exactly once and immediately; inspect for duplicate callback invocations (logs show two exchanges with the same code).
  4. Curl the token endpoint manually with the same payload to see the provider's raw error (invalid_grant, invalid_client, etc.).

Example fix

# reproduce to see the provider's real error
curl -X POST https://provider.example/oauth/token \
  -d grant_type=authorization_code -d client_id=ID -d client_secret=SECRET \
  -d code=THE_CODE -d redirect_uri='https://your-app/v1/auth/oauth/callback'
Defensive patterns

Strategy: try-catch

Validate before calling

def exchange_precheck(provider_cfg, redirect_uri):
    assert provider_cfg["client_id"] and provider_cfg["client_secret"]
    assert provider_cfg["token_url"].startswith("https://")
    assert redirect_uri == provider_cfg["registered_redirect_uri"]  # byte-exact

Try / catch

try:
    token = client.exchange_code_for_token(code)
except ValueError as e:
    msg = str(e)
    if "invalid_grant" in msg:
        restart_authorization()   # code expired/used - get a fresh one
    elif "invalid_client" in msg:
        raise ConfigError("OAuth client secret wrong - update provider config")
    else:
        raise

Prevention

When it happens

Trigger: redirect_uri differs byte-for-byte from the one registered with the provider (trailing slash, http vs https, port); client_secret wrong or rotated; the authorization code was already used once or expired (typically ~1-10 min); code from a different client_id; network/TLS failure reaching token_url; provider returns non-JSON on error making response.json() raise.

Common situations: Local development behind a proxy where the externally visible URL differs from redirect_uri; secrets rotated without updating RAGFlow config; double callback firing (browser retry, duplicate route) consuming the one-time code; clock/token endpoint typos in hand-written OAuth config.

Related errors


AI-assisted analysis of infiniflow/ragflow@554fb1133a (2026-08-15). Data as JSON: /api/errors/55e2712bbf2405be. Report an issue: GitHub.