{"record":{"id":"93abc9590fc87e99","repo":"different-ai/openwork","slug":"oauth-token-exchange-failed","errorCode":"oauth_token_exchange_failed","errorMessage":"${input.provider.displayName} rejected the OAuth token exchange. Try Connect again; if it still fails, contact support with the diagnostic reference.","messagePattern":"(.+?) rejected the OAuth token exchange\\. Try Connect again; if it still fails, contact support with the diagnostic reference\\.","errorType":"error_code","errorClass":"OAuthTokenExchangeError","httpStatus":null,"severity":"error","filePath":"ee/apps/den-api/src/capability-sources/generic-oauth.ts","lineNumber":407,"sourceCode":"      body: input.params,\n      signal: AbortSignal.timeout(TOKEN_REQUEST_TIMEOUT_MS),\n    })\n  } catch {\n    throw new OAuthTokenExchangeError(\n      `${input.provider.displayName} token endpoint could not be reached before the request deadline.`,\n      \"oauth_token_endpoint_unreachable\",\n    )\n  }\n\n  const text = await readBoundedTokenResponse(response)\n  let body: unknown\n  try {\n    body = JSON.parse(text)\n  } catch {\n    body = text\n  }\n  if (!response.ok) {\n    throw oauthTokenExchangeErrorFromResponse({\n      provider: input.provider,\n      status: response.status,\n      body,\n    })\n  }\n  return parseOAuthTokenResponse(body)\n}\n\nexport async function exchangeCodeForTokens(input: {\n  provider: NativeOAuthProviderConfig\n  client: OrgOAuthClientRow\n  code: string\n  redirectUri: string\n  codeVerifier?: string\n}): Promise<TokenResponse> {\n  const params = new URLSearchParams({\n    grant_type: \"authorization_code\",\n    code: input.code,","sourceCodeStart":389,"sourceCodeEnd":425,"githubUrl":"https://github.com/different-ai/openwork/blob/2b7df46e8ae1517d64c896c7793d2d52ec845669/ee/apps/den-api/src/capability-sources/generic-oauth.ts#L389-L425","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before: redirect_uri in token exchange differs from the authorize step\nbody: { grant_type: 'authorization_code', code, client_id, client_secret }\n// after: echo the exact redirect_uri used during authorize\nbody: { grant_type: 'authorization_code', code, client_id, client_secret, redirect_uri: authorizeRedirectUri }","handlingStrategy":"try-catch","validationCode":"// before exchanging: check code and config presence\nif (!authorizationCode) throw new Error('missing authorization code')\nif (!clientId || !clientSecret || !redirectUri) throw new Error('incomplete OAuth app config')","typeGuard":"function isTokenExchangeError(e: unknown): e is { code: 'oauth_token_exchange_failed'; status: number; body: unknown } {\n  return typeof e === 'object' && e !== null && (e as { code?: string }).code === 'oauth_token_exchange_failed'\n}","tryCatchPattern":"try {\n  tokens = await exchangeCodeForTokens(input)\n} catch (error) {\n  if (isTokenExchangeError(error)) {\n    // restart Connect flow; log provider status/body for support reference\n  } else throw error\n}","preventionTips":["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."],"tags":["oauth","token-exchange","auth","provider"],"backgroundTag":"oauth-token-exchange-failed","analyzedSha":"2b7df46e8ae1517d64c896c7793d2d52ec845669","analyzedAt":"2026-09-01T07:59:23.713Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}