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

  1. Retry the Connect flow from scratch to get a fresh authorization code and exchange it immediately.
  2. Verify the provider OAuth app's client_id/client_secret and registered redirect_uri exactly match the Den configuration.
  3. Check the provider response body in the diagnostic for the OAuth error code (invalid_grant, invalid_client, etc.) and fix the matching misconfiguration.
  4. 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

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


AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01). Data as JSON: /api/errors/93abc9590fc87e99. Report an issue: GitHub.