Significant-Gravitas/AutoGPT · error · HTTPException
Invalid or expired state token
Error message
Invalid or expired state token
What it means
Raised (HTTP 400) by the external OAuth complete endpoint when `creds_manager.store.verify_state_token(user_id, state_token, provider)` returns None. Verification is a constant-time token comparison plus a provider match and an expiry check; a valid state is also single-use (it is removed on success), so replaying a completed flow fails the same way.
Source
Thrown at autogpt_platform/backend/backend/api/external/v1/integrations.py:419
auth: APIAuthorizationInfo = Security(
require_permission(APIKeyPermission.MANAGE_INTEGRATIONS)
),
) -> OAuthCompleteResponse:
"""
Complete an OAuth flow by exchanging the authorization code for tokens.
This endpoint should be called after the user has authorized the application
and been redirected back to the external app's callback URL with an
authorization code.
"""
# Verify state token
valid_state = await creds_manager.store.verify_state_token(
auth.user_id, request.state_token, provider
)
if not valid_state:
logger.warning(f"Invalid or expired state token for provider {provider}")
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Invalid or expired state token",
)
# Verify this is an external flow (callback_url must be set)
if not valid_state.callback_url:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="State token was not created for external OAuth flow",
)
# Get OAuth handler with the original callback URL
handler = _get_oauth_handler_for_external(provider, valid_state.callback_url)
try:
scopes = valid_state.scopes
scopes = handler.handle_default_scopes(scopes)
View on GitHub (pinned to 9c8bb5550f)
Solutions
- Re-initiate the flow via the authorize endpoint to get a fresh state token and complete the callback promptly.
- Make the callback idempotent-safe on the client: never retry a used state; on 400 'Invalid or expired state token' always restart from authorize.
- Verify you pass the same provider in the URL as was used at initiation (provider must match `provider_matches`).
- Check the state token is transmitted unmodified (no trimming, re-encoding, or JSON escaping issues).
Example fix
# before: reusing an old/consumed state
POST /integrations/github/oauth/callback
{"state_token": "st_used_or_expired", "code": "..."} # 400
# after: restart the flow
POST /integrations/github/oauth/authorize -> new state_token
POST /integrations/github/oauth/callback {"state_token": new, "code": "..."} Defensive patterns
Strategy: try-catch
Try / catch
try:
result = client.post(f"/integrations/{provider}/oauth/callback", json={"state_token": state, "code": code})
except HTTPError as e:
if e.response.status_code == 400 and "state token" in e.response.text.lower():
# state is single-use and expiring: always restart the flow
state = start_new_flow(provider)
result = client.post(f"/integrations/{provider}/oauth/callback", json={"state_token": state, "code": new_code})
else:
raise Prevention
- Complete the callback immediately after the provider redirect; states expire and are single-use.
- Never retry a callback with the same state token; on 400 restart from authorize.
- Keep provider consistent between authorize and callback calls.
When it happens
Trigger: POST `/api/external-api/v1/integrations/{provider}/oauth/callback` with a state_token that is expired (past `expires_at`), already consumed, issued for a different provider, issued for a different user, or corrupted/missing. Calling the callback endpoint twice with the same state is the classic replay case.
Common situations: User sits on the provider consent screen longer than the state TTL; double-submit of the callback (refresh or retry after a network error); state serialized incorrectly by the external app (truncation/encoding); mixing states between the internal platform OAuth flow and the external flow.
Related errors
- State token was not created for external OAuth flow
- Server did not return an access token for the Google Drive p
- Integration with provider '{provider_name}' is not configure
- Callback URL origin is not allowed. Allowed origins: {settin
- Provider '{provider}' not found
AI-assisted analysis of Significant-Gravitas/AutoGPT@9c8bb5550f (2026-08-14).
Data as JSON: /api/errors/f6c5cdeb757d6ee9.
Report an issue: GitHub.