calcom/cal.diy · error · UnauthorizedException

${googleCalendar.error?.message}

Error message

${googleCalendar.error?.message}

What it means

Thrown by GoogleCalendarService.checkIfCalendarConnected (gcal.service.ts:122) as UnauthorizedException (HTTP 401) when the connected Google calendar object carries a truthy error.message produced by the upstream integration. The raw upstream message is re-thrown verbatim, so the actual cause (expired access token, quota, Google API error) is embedded in the response body. Mirror of error 363.

Solutions

  1. Read the dynamic message in the 401 response body — it carries the integration's verbatim error text.
  2. If the message is auth/token-related, re-run the OAuth connect→save flow (see error 377).
  3. If the message indicates quota/transient failure, retry with backoff.

Example fix

// before: generic error handling
try { await api.get('/v2/calendars/google_calendar/check'); }
catch (e) { throw e; }

// after: branch on the upstream message
try { await api.get('/v2/calendars/google_calendar/check'); }
catch (e) {
  const msg = e.response?.data?.message ?? '';
  if (/token|credential|401|unauthorized/i.test(msg)) await reAuthGoogle();
  else if (/quota|rate|retry/i.test(msg)) await retryWithBackoff(check);
  else throw e;
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Inspect the upstream error from the list before calling check
const { data } = await api.get('/v2/calendars');
const gcal = data.connectedCalendars.find(c => c.integration?.type === 'google_calendar');
if (gcal?.error?.message) {
  throw new UpstreamError(gcal.error.message);
}

Type guard

function hasUpstreamError(conn) {
  return typeof conn?.error?.message === 'string' && conn.error.message.length > 0;
}

Try / catch

try {
  await api.get('/v2/calendars/google_calendar/check');
} catch (e) {
  const msg = e.response?.data?.message ?? '';
  if (e.response?.status === 401) {
    if (/token|credential|unauthorized|401/i.test(msg)) await reAuthGoogle();
    else await retryWithBackoff(() => api.get('/v2/calendars/google_calendar/check'));
    return;
  }
  throw e;
}

Prevention

When it happens

Trigger: Google Calendar API returned 401 because the access token expired and refresh failed; daily quota exceeded; transient Google API outage; revoked consent surfaced as an integration error.

Common situations: Expired access token; user revoked access in Google account; Google API rate limiting; intermittent failures under load.

Related errors


AI-assisted analysis of calcom/cal.diy@176037d0af (2026-08-12). Data as JSON: /api/errors/6e0de3ad81e9bad4. Report an issue: GitHub.

Appendix: source

Thrown at apps/api/v2/src/platform/calendars/services/gcal.service.ts:122

    );

    if (!gcalCredentials) {
      throw new BadRequestException("Credentials for google_calendar not found.");
    }

    if (gcalCredentials.invalid) {
      throw new BadRequestException("Invalid google OAuth credentials.");
    }

    const { connectedCalendars } = await this.calendarsService.getCalendars(userId);
    const googleCalendar = connectedCalendars.find(
      (cal: { integration: { type: string } }) => cal.integration.type === GOOGLE_CALENDAR_TYPE
    );
    if (!googleCalendar) {
      throw new UnauthorizedException("Google Calendar not connected.");
    }
    if (googleCalendar.error?.message) {
      throw new UnauthorizedException(googleCalendar.error?.message);
    }

    return { status: SUCCESS_STATUS };
  }

  async saveCalendarCredentialsAndRedirect(
    code: string,
    accessToken: string,
    origin: string,
    redir?: string,
    isDryRun?: boolean
  ) {
    // User chose not to authorize your app or didn't authorize your app
    // redirect directly without oauth code
    if (!code || code === "undefined") {
      return { url: redir || origin };
    }

View on GitHub (pinned to 176037d0af)