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
The POST /integrations/{provider}/callback endpoint caught an exception while exchanging the OAuth authorization code for tokens (handler.exchange_code_for_tokens), and re-raised it as HTTP 400 with the underlying error text appended. Any failure inside the token-exchange block — network error, invalid/expired code, bad client config, missing code_verifier — funnels into this one message.
Source
Thrown at autogpt_platform/backend/backend/api/features/integrations/router.py:355
# Linear returns scopes as a single string with spaces, so we need to split them
# TODO: make a bypass of this part of the OAuth handler
if len(credentials.scopes) == 1 and " " in credentials.scopes[0]:
credentials.scopes = credentials.scopes[0].split(" ")
# Check if the granted scopes are sufficient for the requested scopes
if not set(scopes).issubset(set(credentials.scopes)):
# For now, we'll just log the warning and continue
logger.warning(
f"Granted scopes {credentials.scopes} for provider {provider.value} "
f"do not include all requested scopes {scopes}"
)
except Exception as e:
logger.error(
f"OAuth2 Code->Token exchange failed for provider {provider.value}: {e}"
)
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"OAuth2 callback failed to exchange code for tokens: {str(e)}",
)
# TODO: Allow specifying `title` to set on `credentials`
credentials = await _merge_or_create_credential(
user_id, provider, credentials, valid_state.credential_id
)
logger.debug(
f"Successfully processed OAuth callback for user {user_id} "
f"and provider {provider.value}"
)
return to_meta_response(credentials)
# Bound the first-time sweep so a slow upstream (e.g. Ayrshare) can't hangView on GitHub (pinned to 9c8bb5550f)
Solutions
- Read the appended {str(e)} and the server log line 'OAuth2 Code->Token exchange failed' — the provider's own error (e.g. invalid_grant, invalid_client) names the real cause
- If invalid_grant: restart the OAuth flow at GET /integrations/{provider}/login to get a fresh code and state token; do not replay the old callback
- If invalid_client / unauthorized: verify the provider's client id/secret env vars are set in the backend environment and match the provider console
- If the code_verifier mismatches: confirm the state_token passed is the one from the same login session (state stores the PKCE verifier) and that it hasn't expired
- Check the redirect URI registered with the provider exactly matches the callback URL the backend advertises
Example fix
// before: replaying an old callback body
await client.post(f'/integrations/{provider}/callback', json={'code': old_code, 'state_token': old_state})
// after: mint a fresh login, then callback immediately
login = await client.post(f'/integrations/{provider}/login', json={'scopes': [...]})
# ...user completes consent...
await client.post(f'/integrations/{provider}/callback', json={'code': fresh_code, 'state_token': login.state_token}) Defensive patterns
Strategy: retry
Try / catch
resp = await client.post(...)
if resp.status_code == 400 and 'exchange code for tokens' in resp.json()['detail']:
# the suffix carries the provider error; treat expired/used codes as retryable
login = await fresh_login(provider) # restart flow
else:
resp.raise_for_status() Prevention
- Never cache or replay authorization codes — they are single-use
- Keep the state_token and code paired from the same login response
- Set provider client id/secret env vars consistently across local, docker, and CI environments
When it happens
Trigger: POST /integrations/{provider}/callback with a code that is expired, already used, or mismatched to the client_id/redirect_uri; missing/misconfigured OAuth client env vars (e.g. GOOGLE_CLIENT_*_ID/SECRET); PKCE code_verifier lost between login and callback (stale state token); provider API unreachable (5xx/timeout).
Common situations: Env vars for the provider not set in backend/.env (works locally, fails in docker), clock skew or long delay between consent and callback so the code expires, re-using a callback URL after the state token was already consumed, redirect URI registered in the provider console not matching the callback URL the backend sends.
Related errors
- Picker tokens are only available for OAuth2 credentials
- Credential has no access token; reconnect the account
- Credential does not grant any scope eligible for the picker.
- Provider-runtime credentials cannot be created directly
- The ChatGPT verification link is invalid. Close this window
AI-assisted analysis of Significant-Gravitas/AutoGPT@9c8bb5550f (2026-08-14).
Data as JSON: /api/errors/a4d3f0790c2388a9.
Report an issue: GitHub.