different-ai/openwork · error · OAuthTokenExchangeError
oauth_token_exchange_failed
oauth_token_exchange_failed
Error message
${input.provider.displayName} rejected the OAuth token exchange. Try Connect again; if it still fails, contact support with the diagnostic reference. What it means
postTokenRequest in generic-oauth.ts performs the provider's OAuth token endpoint request (used by exchangeCodeForTokens and refreshTokens). If the response is not ok, oauthTokenExchangeErrorFromResponse wraps the status and parsed (or raw) body into an oauth_token_exchange_failed error telling the user to retry Connect or contact support with the diagnostic reference.
Source
Thrown at ee/apps/den-api/src/capability-sources/generic-oauth.ts:407
body: input.params,
signal: AbortSignal.timeout(TOKEN_REQUEST_TIMEOUT_MS),
})
} catch {
throw new OAuthTokenExchangeError(
`${input.provider.displayName} token endpoint could not be reached before the request deadline.`,
"oauth_token_endpoint_unreachable",
)
}
const text = await readBoundedTokenResponse(response)
let body: unknown
try {
body = JSON.parse(text)
} catch {
body = text
}
if (!response.ok) {
throw oauthTokenExchangeErrorFromResponse({
provider: input.provider,
status: response.status,
body,
})
}
return parseOAuthTokenResponse(body)
}
export async function exchangeCodeForTokens(input: {
provider: NativeOAuthProviderConfig
client: OrgOAuthClientRow
code: string
redirectUri: string
codeVerifier?: string
}): Promise<TokenResponse> {
const params = new URLSearchParams({
grant_type: "authorization_code",
code: input.code,View on GitHub (pinned to 2b7df46e8a)
Solutions
- Retry the Connect flow from scratch to get a fresh authorization code and exchange it immediately.
- Verify the provider OAuth app's client_id/client_secret and registered redirect_uri exactly match the Den configuration.
- Check the provider response body in the diagnostic for the OAuth error code (invalid_grant, invalid_client, etc.) and fix the matching misconfiguration.
- If refresh fails with invalid_grant, the refresh token is revoked — the user must reconnect; contact support with the diagnostic reference if it persists.
Example fix
// before: redirect_uri in token exchange differs from the authorize step
body: { grant_type: 'authorization_code', code, client_id, client_secret }
// after: echo the exact redirect_uri used during authorize
body: { grant_type: 'authorization_code', code, client_id, client_secret, redirect_uri: authorizeRedirectUri } Defensive patterns
Strategy: try-catch
Validate before calling
// before exchanging: check code and config presence
if (!authorizationCode) throw new Error('missing authorization code')
if (!clientId || !clientSecret || !redirectUri) throw new Error('incomplete OAuth app config') Type guard
function isTokenExchangeError(e: unknown): e is { code: 'oauth_token_exchange_failed'; status: number; body: unknown } {
return typeof e === 'object' && e !== null && (e as { code?: string }).code === 'oauth_token_exchange_failed'
} Try / catch
try {
tokens = await exchangeCodeForTokens(input)
} catch (error) {
if (isTokenExchangeError(error)) {
// restart Connect flow; log provider status/body for support reference
} else throw error
} Prevention
- Exchange authorization codes immediately — they are single-use and short-lived.
- Keep redirect_uri byte-identical between authorize and token requests.
- Rotate client secrets consistently across environments.
- Treat invalid_grant on refresh as 'user must reconnect', not retry.
When it happens
Trigger: Calling the code-for-token exchange or a token refresh when the provider's token endpoint returns a non-2xx: invalid/expired authorization code (400), wrong client_id/client_secret (401), redirect_uri mismatch (400), unsupported grant, or refresh token revoked.
Common situations: Reusing an authorization code (they are single-use); provider app credentials rotated or mismatched between environments; redirect URI not registered exactly in the provider's OAuth app; expired authorization code due to slow handshake; revoked refresh token after password change or admin revocation.
Related errors
- AUTH_RESOURCE_DISCOVERY
- AUTH_ISSUER_DISCOVERY
- MCP_PROVIDER_AUTH_REQUIRED
- Failed to sign in to OpenWork Cloud.
- desktop_signup_deprecated
AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01).
Data as JSON: /api/errors/93abc9590fc87e99.
Report an issue: GitHub.