{"record":{"id":"dccc9ff6f78edced","repo":"calcom/cal.diy","slug":"no-valid-credentials-available-for-google-calendar","errorCode":null,"errorMessage":"No valid credentials available for Google Calendar","messagePattern":"No valid credentials available for Google Calendar","errorType":"http","errorClass":"UnauthorizedException","httpStatus":401,"severity":"error","filePath":"apps/api/v2/src/modules/cal-unified-calendars/services/google-calendar.service.ts","lineNumber":121,"sourceCode":"  /**\n   * Gets an authorized Google Calendar instance\n   * Tries delegation credentials first, falls back to direct OAuth\n   */\n  private async getAuthorizedCalendarInstance(\n    userEmail?: string,\n    oAuthCredentials?: Prisma.JsonValue | undefined,\n    delegationCredential?: { id: string } | null\n  ): Promise<calendar_v3.Calendar> {\n    if (userEmail && delegationCredential?.id) {\n      const delegatedCalendar = await this.getDelegatedCalendarInstance(delegationCredential, userEmail);\n      if (delegatedCalendar) {\n        return delegatedCalendar;\n      }\n    }\n\n    // Fall back to direct OAuth authentication\n    if (!oAuthCredentials) {\n      throw new UnauthorizedException(\"No valid credentials available for Google Calendar\");\n    }\n    const parsedOAuthCredentials = OAuth2UniversalSchema.parse(oAuthCredentials);\n    const oAuth2Client = await this.gCalService.getOAuthClient(this.gCalService.redirectUri);\n    oAuth2Client.setCredentials(parsedOAuthCredentials);\n\n    return new calendar_v3.Calendar({ auth: oAuth2Client });\n  }\n\n  private async getDelegatedCalendarInstance(\n    delegationCredential: { id: string },\n    emailToImpersonate: string\n  ): Promise<calendar_v3.Calendar | null> {\n    try {\n      const oauthClientIdAliasRegex = /\\+[a-zA-Z0-9]{25}/;\n      const cleanEmail = emailToImpersonate.replace(oauthClientIdAliasRegex, \"\");\n\n      const serviceAccountCreds =\n        await DelegationCredentialRepository.findByIdIncludeSensitiveServiceAccountKey({","sourceCodeStart":103,"sourceCodeEnd":139,"githubUrl":"https://github.com/calcom/cal.diy/blob/176037d0afbe572f870a3c702985e7cd83fe6c0c/apps/api/v2/src/modules/cal-unified-calendars/services/google-calendar.service.ts#L103-L139","documentation":"Thrown by getAuthorizedCalendarInstance when neither delegated auth nor direct OAuth can produce a Google Calendar client. The method first attempts service-account delegation (getDelegatedCalendarInstance) when a userEmail and delegationCredential.id are supplied; if that returns null, it falls through and requires oAuthCredentials on the credential record. When credential.key is missing/empty the UnauthorizedException (HTTP 401) fires.","triggerScenarios":"Calling getEventDetails/updateEventDetails with a booking reference whose credential.key is null, or calling getCalendarClientForUser/getCalendarClientByCredentialId for a credential that has a delegationCredentialId pointing at a delegation record whose serviceAccountKey is missing (getDelegatedCalendarInstance swallows the error and returns null) and whose own key field is also empty.","commonSituations":"A credential row was created for delegation but the service-account JSON was never stored; the delegation credential was deleted out of band but delegationCredentialId on the credential still points at it; a migration left credential.key null for users moved to domain-wide delegation.","solutions":["Inspect the credential row (SELECT key, \"delegationCredentialId\", invalid FROM \"AppCredential\" WHERE id=?) and confirm whether key is populated or delegationCredentialId points to a live DelegationCredential with a serviceAccountKey.","If the user is meant to use delegation, ensure the DelegationCredential row has a complete serviceAccountKey (client_email + private_key); otherwise re-run domain-wide delegation setup.","If the user should use direct OAuth, have them reconnect Google Calendar so credential.key is populated with a valid OAuth token JSON.","As a caller, pre-check that either credential.key is truthy or (credential.user.email and credential.delegationCredentialId) are set before invoking calendar operations."],"exampleFix":"// before: caller blindly calls the service\nconst cal = await googleCalendarService.getCalendarClientForUser(userId);\n\n// after: guard before calling\nconst cred = await credentialsRepository.findCredentialWithDelegationByTypeAndUserId(GOOGLE_CALENDAR_TYPE, userId);\nif (!cred || cred.invalid || (!cred.key && !(cred.user?.email && cred.delegationCredentialId))) {\n  throw new UnauthorizedException('Reconnect Google Calendar before proceeding.');\n}","handlingStrategy":"validation","validationCode":"// Validate a credential can yield a calendar client before calling the service.\nfunction canAuthorizeCredential(cred: { key: unknown; user?: { email?: string | null }; delegationCredentialId?: string | null; invalid?: boolean }): boolean {\n  if (cred.invalid) return false;\n  const hasOAuth = Boolean(cred.key);\n  const hasDelegation = Boolean(cred.user?.email && cred.delegationCredentialId);\n  return hasOAuth || hasDelegation;\n}\n\n// usage\nconst cred = await credentialsRepository.findCredentialWithDelegationByTypeAndUserId(GOOGLE_CALENDAR_TYPE, userId);\nif (!cred || !canAuthorizeCredential(cred)) {\n  throw new UnauthorizedException('Reconnect Google Calendar before proceeding.');\n}","typeGuard":"import type { AppCredential } from '@prisma/client';\n\nfunction hasUsableOAuthKey(cred: AppCredential): boolean {\n  return cred.key != null && typeof cred.key === 'object' && Object.keys(cred.key).length > 0;\n}\n\nfunction hasDelegationContext(cred: AppCredential & { user?: { email?: string | null } }): boolean {\n  return Boolean(cred.delegationCredentialId && cred.user?.email);\n}\n\nfunction isCalendarClientReady(cred: AppCredential & { user?: { email?: string | null } }): cred is AppCredential & { user: { email: string } } {\n  return !cred.invalid && (hasUsableOAuthKey(cred) || hasDelegationContext(cred));\n}","tryCatchPattern":"try {\n  const cal = await googleCalendarService.getCalendarClientForUser(userId);\n} catch (e) {\n  if (e instanceof UnauthorizedException && e.message.includes('No valid credentials')) {\n    // prompt reconnect; this is unrecoverable without user action\n    return { status: 'reconnect_required', provider: 'google_calendar' };\n  }\n  throw e;\n}","preventionTips":["Before any calendar operation, fetch the credential once and reuse it; do not let the service re-fetch and fail late.","When provisioning domain-wide delegation, write the serviceAccountKey atomically with the delegationCredential row.","Run a periodic job that flags credentials whose delegationCredentialId points at a missing row.","Treat a null credential.key as a hard error in onboarding, not a soft warning."],"tags":["google-calendar","oauth","delegation","credentials","nestjs","auth"],"backgroundTag":null,"analyzedSha":"176037d0afbe572f870a3c702985e7cd83fe6c0c","analyzedAt":"2026-08-12T19:12:41.464Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}