{"record":{"id":"f4b232ccc80fa038","repo":"calcom/cal.diy","slug":"invalid-google-oauth-credentials","errorCode":null,"errorMessage":"Invalid google OAuth credentials.","messagePattern":"Invalid google OAuth credentials\\.","errorType":"http","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"apps/api/v2/src/platform/calendars/services/gcal.service.ts","lineNumber":111,"sourceCode":"\n    const { client_id, client_secret } = this.gcalResponseSchema.parse(app.keys);\n\n    const oAuth2Client = new OAuth2Client(client_id, client_secret, redirectUri);\n    return oAuth2Client;\n  }\n\n  async checkIfCalendarConnected(userId: number): Promise<{ status: typeof SUCCESS_STATUS }> {\n    const gcalCredentials = await this.credentialRepository.findCredentialByTypeAndUserId(\n      \"google_calendar\",\n      userId\n    );\n\n    if (!gcalCredentials) {\n      throw new BadRequestException(\"Credentials for google_calendar not found.\");\n    }\n\n    if (gcalCredentials.invalid) {\n      throw new BadRequestException(\"Invalid google OAuth credentials.\");\n    }\n\n    const { connectedCalendars } = await this.calendarsService.getCalendars(userId);\n    const googleCalendar = connectedCalendars.find(\n      (cal: { integration: { type: string } }) => cal.integration.type === GOOGLE_CALENDAR_TYPE\n    );\n    if (!googleCalendar) {\n      throw new UnauthorizedException(\"Google Calendar not connected.\");\n    }\n    if (googleCalendar.error?.message) {\n      throw new UnauthorizedException(googleCalendar.error?.message);\n    }\n\n    return { status: SUCCESS_STATUS };\n  }\n\n  async saveCalendarCredentialsAndRedirect(\n    code: string,","sourceCodeStart":93,"sourceCodeEnd":129,"githubUrl":"https://github.com/calcom/cal.diy/blob/176037d0afbe572f870a3c702985e7cd83fe6c0c/apps/api/v2/src/platform/calendars/services/gcal.service.ts#L93-L129","documentation":"Thrown by GoogleCalendarService.checkIfCalendarConnected (gcal.service.ts:111) as BadRequestException (HTTP 400) when the google_calendar credential row exists but its 'invalid' flag is true. A prior token-refresh or API call failed and flagged the credential; Google OAuth refresh tokens also expire after 6 months of inactivity.","triggerScenarios":"User revoked access from their Google account security page; refresh token expired due to long inactivity; OAuth scopes changed requiring re-consent; a background refresh failure set the flag.","commonSituations":"Revoked app access; dormant user returning after months; Google project credential rotation.","solutions":["Re-run the Google OAuth connect→save flow, which generates a fresh credential and clears invalid (prompt:'consent' is already set to force new tokens).","If you changed OAuth scopes, verify the new scopes match CALENDAR_SCOPES in gcal.service.ts.","After save, confirm invalid=false before retrying check."],"exampleFix":"// before: re-checking a flagged credential\nawait api.get('/v2/calendars/google_calendar/check'); // 400 'Invalid google OAuth credentials.'\n\n// after: force a fresh consent flow\nconst { data } = await api.post('/v2/calendars/google_calendar/connect', {});\n// user re-authorizes at data.authUrl (prompt=consent)\nawait api.post('/v2/calendars/google_calendar/save', { code, ... });\nawait api.get('/v2/calendars/google_calendar/check'); // 200","handlingStrategy":"validation","validationCode":"// Detect an invalid google credential via the list before check\nconst { data } = await api.get('/v2/calendars');\nconst gcal = data.connectedCalendars.find(c => c.integration?.type === 'google_calendar');\nif (gcal?.error || gcal === undefined) {\n  // invalid or missing — re-run consent\n  await startGoogleOAuth();\n}","typeGuard":"function isCredentialValid(cred) {\n  return !!cred && cred.invalid === false;\n}","tryCatchPattern":"try {\n  await api.get('/v2/calendars/google_calendar/check');\n} catch (e) {\n  if (e.response?.status === 400 && /invalid/i.test(e.response?.data?.message)) {\n    // force fresh consent (prompt=consent is already set server-side)\n    await startGoogleOAuth();\n    return;\n  }\n  throw e;\n}","preventionTips":["Re-run the OAuth consent flow whenever the credential is flagged invalid — do not retry check.","Keep OAuth scopes stable; scope changes require re-consent.","Background-refresh failures should invalidate the flag and notify the user, not stay silent."],"tags":["google-calendar","oauth","invalid-flag","token-refresh","http-400"],"backgroundTag":null,"analyzedSha":"176037d0afbe572f870a3c702985e7cd83fe6c0c","analyzedAt":"2026-08-12T19:12:41.464Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}