Significant-Gravitas/AutoGPT · error · HTTPException

OAuth2 callback failed to exchange code for tokens: {str(e)}

Error message

OAuth2 callback failed to exchange code for tokens: {str(e)}

What it means

Raised (HTTP 400) by the external OAuth complete endpoint when `handler.exchange_code_for_tokens(...)` (or the subsequent default-scopes/scope-normalization block) raises any exception. The original exception is logged as 'OAuth2 Code->Token exchange failed for provider {provider}' and its string is surfaced in the detail, so the provider's error message (invalid_grant, redirect_uri mismatch, network failure) is embedded verbatim.

Source

Thrown at autogpt_platform/backend/backend/api/external/v1/integrations.py:455

        credentials = await handler.exchange_code_for_tokens(
            request.code, scopes, valid_state.code_verifier
        )

        # Handle Linear's space-separated scopes
        if len(credentials.scopes) == 1 and " " in credentials.scopes[0]:
            credentials.scopes = credentials.scopes[0].split(" ")

        # Check scope mismatch
        if not set(scopes).issubset(set(credentials.scopes)):
            logger.warning(
                f"Granted scopes {credentials.scopes} for provider {provider} "
                f"do not include all requested scopes {scopes}"
            )

    except Exception as e:
        logger.error(f"OAuth2 Code->Token exchange failed for provider {provider}: {e}")
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail=f"OAuth2 callback failed to exchange code for tokens: {str(e)}",
        )

    # Store credentials
    await creds_manager.create(auth.user_id, credentials)

    logger.info(f"Successfully completed external OAuth for provider {provider}")

    return OAuthCompleteResponse(
        credentials_id=credentials.id,
        provider=credentials.provider,
        type=credentials.type,
        title=credentials.title,
        scopes=credentials.scopes,
        username=credentials.username,
        state_metadata=valid_state.state_metadata,
    )

View on GitHub (pinned to 9c8bb5550f)

Solutions

  1. Read the detail message: 'invalid_grant' or 'code already redeemed' means restart the flow from authorize; 'redirect_uri mismatch' means align the callback URL used in both steps with the provider app config.
  2. Complete the callback immediately after the user is redirected back; never retry with the same code.
  3. Verify the provider OAuth app's allowed redirect URIs include the external callback origin.
  4. Check backend logs (`OAuth2 Code->Token exchange failed for provider ...`) for the full provider response when the detail is opaque.

Example fix

# before: retrying a consumed authorization code
POST /integrations/github/oauth/callback {"state_token": st, "code": "reused_code"}  # 400

# after: get a fresh code by restarting the flow
POST /integrations/github/oauth/authorize -> user authorizes -> new code
POST /integrations/github/oauth/callback {"state_token": new_st, "code": "fresh_code"}
Defensive patterns

Strategy: retry

Try / catch

try:
    creds = client.post(f"/integrations/{provider}/oauth/callback", json={"state_token": state, "code": code})
except HTTPError as e:
    detail = e.response.text
    if "invalid_grant" in detail or "code" in detail.lower():
        # auth codes are single-use & short-lived: restart the whole flow
        state, login_url = client.post(f"/integrations/{provider}/oauth/authorize", json=payload).json().values()
        raise RestartFlow(login_url)  # send user back to provider consent
    if "redirect_uri" in detail:
        raise ConfigError("align callback URL with provider app settings") from e
    raise

Prevention

When it happens

Trigger: POST `/integrations/{provider}/oauth/callback` with an expired or already-used authorization code, a redirect_uri that differs from the one used at authorize time, wrong client credentials, a provider outage/network error, or a provider response that fails parsing.

Common situations: Redeeming an auth code after the typical 5–10 minute provider TTL; retrying the callback after a first failure (codes are single-use); the platform's provider OAuth app redirect URI not covering the external callback; clock skew or revoked provider app.

Related errors


AI-assisted analysis of Significant-Gravitas/AutoGPT@9c8bb5550f (2026-08-14). Data as JSON: /api/errors/d1517cd80d55e63b. Report an issue: GitHub.