{"record":{"id":"ce8df653b24d4c78","repo":"twentyhq/twenty","slug":"token-exchange-failed-response-status-respon","errorCode":null,"errorMessage":"Token exchange failed: ${response.status} ${response.statusText}","messagePattern":"Token exchange failed: (.+?) (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/twenty-sdk/src/cli/utilities/auth/exchange-credentials-for-tokens.ts","lineNumber":20,"sourceCode":"\nexport const exchangeCredentialsForTokens = async (\n  configService: ConfigService,\n  params: { clientId: string; clientSecret: string },\n): Promise<{ accessToken: string; refreshToken?: string }> => {\n  const config = await configService.getConfig();\n\n  const response = await fetch(`${config.apiUrl}/oauth/token`, {\n    method: 'POST',\n    headers: { 'Content-Type': 'application/json' },\n    body: JSON.stringify({\n      grant_type: 'client_credentials',\n      client_id: params.clientId,\n      client_secret: params.clientSecret,\n    }),\n  });\n\n  if (!response.ok) {\n    throw new Error(\n      `Token exchange failed: ${response.status} ${response.statusText}`,\n    );\n  }\n\n  const data = (await response.json()) as {\n    access_token: string;\n    refresh_token?: string;\n  };\n\n  await configService.setConfig({\n    appAccessToken: data.access_token,\n    ...(data.refresh_token ? { appRefreshToken: data.refresh_token } : {}),\n  });\n\n  return {\n    accessToken: data.access_token,\n    refreshToken: data.refresh_token,\n  };","sourceCodeStart":2,"sourceCodeEnd":38,"githubUrl":"https://github.com/twentyhq/twenty/blob/1f5dd2bbd2a8da3419c8cfd52dd545c0024df1a6/packages/twenty-sdk/src/cli/utilities/auth/exchange-credentials-for-tokens.ts#L2-L38","documentation":"Thrown by exchangeCredentialsForTokens when the OAuth token endpoint returns a non-2xx HTTP status. The function POSTs client_credentials grant type to {apiUrl}/oauth/token, and any non-ok response is treated as a token exchange failure. The HTTP status code and status text are included for diagnostics.","triggerScenarios":"The OAuth /oauth/token endpoint returns 401 (invalid client_id/client_secret), 400 (malformed request), 403 (forbidden), 500 (server error), or any other non-2xx status. This happens during the SDK CLI authentication flow when exchanging app registration credentials for access tokens.","commonSituations":"The client_secret is wrong, expired, or was regenerated since it was stored. The client_id doesn't match a valid app registration. The apiUrl is wrong (pointing to a different environment). The OAuth server is temporarily down. The client_credentials grant is not enabled for this app registration.","solutions":["If status is 401: re-run the SDK registration command to get fresh clientId/clientSecret credentials.","If status is 400: verify the apiUrl in config is correct and points to the right Twenty instance.","If status is 500/502/503: the server is likely down or overloaded — retry after a brief wait.","Check that the client_secret was copied completely (no truncation) and has no leading/trailing whitespace.","Ensure the apiUrl uses the correct protocol and path (https://, no trailing slash issues)."],"exampleFix":"// before\nconst { accessToken } = await exchangeCredentialsForTokens(configService, {\n  clientId,\n  clientSecret,\n});\n\n// after — handle specific HTTP statuses\ntry {\n  const { accessToken } = await exchangeCredentialsForTokens(configService, {\n    clientId, clientSecret,\n  });\n} catch (e) {\n  if (e.message.includes('401')) {\n    console.error('Invalid credentials. Re-run registration to get a new client secret.');\n  }\n  throw e;\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  const { accessToken } = await exchangeCredentialsForTokens(configService, { clientId, clientSecret });\n} catch (error) {\n  if (error instanceof Error && error.message.startsWith('Token exchange failed')) {\n    const status = error.message.match(/HTTP (\\d+)/)?.[1];\n    if (status === '401') {\n      console.error('Invalid client credentials. Re-run registration to obtain new secrets.');\n    } else if (status?.startsWith('5')) {\n      console.error('Server error. Retry in a moment.');\n    }\n    process.exit(1);\n  }\n  throw error;\n}","preventionTips":["Store client_secret securely and completely — avoid truncation during copy.","Re-run the registration command if credentials may have been rotated.","Verify the apiUrl in config matches the environment (staging vs production)."],"tags":["sdk-cli","oauth","authentication","network"],"backgroundTag":null,"analyzedSha":"1f5dd2bbd2a8da3419c8cfd52dd545c0024df1a6","analyzedAt":"2026-08-12T15:37:27.593Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}