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.

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)

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.