calcom/cal.diy · error · UnauthorizedException

${office365Calendar.error?.message}

Error message

${office365Calendar.error?.message}

What it means

Final guard in OutlookService.checkIfCalendarConnected: the connected calendar entry exists but carries an `error.message` set by the integration layer when a live Microsoft Graph call failed. That message is forwarded verbatim in UnauthorizedException, so the literal text is determined by what Graph returned (e.g. 'InvalidAuthenticationToken', 'Access token has expired', 'Resource could not be found'). This is the same pass-through pattern used in the Google and ICS check methods.

Source

Thrown at apps/api/v2/src/platform/calendars/services/outlook.service.ts:110

    );

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

    if (office365CalendarCredentials.invalid) {
      throw new BadRequestException("Invalid office 365 calendar credentials.");
    }

    const { connectedCalendars } = await this.calendarsService.getCalendars(userId);
    const office365Calendar = connectedCalendars.find(
      (cal: { integration: { type: string } }) => cal.integration.type === OFFICE_365_CALENDAR_TYPE
    );
    if (!office365Calendar) {
      throw new UnauthorizedException("Office 365 calendar not connected.");
    }
    if (office365Calendar.error?.message) {
      throw new UnauthorizedException(office365Calendar.error?.message);
    }

    return {
      status: SUCCESS_STATUS,
    };
  }

  async getOAuthCredentials(code: string) {
    const scopes = ["offline_access", "Calendars.Read", "Calendars.ReadWrite"];
    const { client_id, client_secret } = await this.calendarsService.getAppKeys(OFFICE_365_CALENDAR_ID);

    const toUrlEncoded = (payload: Record<string, string>) =>
      Object.keys(payload)
        .map((key) => `${key}=${encodeURIComponent(payload[key])}`)
        .join("&");

    const body = toUrlEncoded({
      client_id,

View on GitHub (pinned to 176037d0af)

Solutions

  1. Read the forwarded message; it is the exact Graph error and points at the fix (expiry -> reconnect, scope -> update app registration).
  2. Re-run the Office 365 connect flow to mint a fresh credential with a valid refresh token.
  3. In the Microsoft app registration, confirm delegated scopes still include Calendars.ReadWrite and offline_access.
Defensive patterns

Strategy: try-catch

Validate before calling

const { connectedCalendars } = await calendarsService.getCalendars(userId);
const o365 = connectedCalendars.find(c => c.integration.type === OFFICE_365_CALENDAR_TYPE);
if (o365?.error?.message) {
  // surface o365.error.message and offer reconnect
}

Try / catch

try {
  await outlookService.checkIfCalendarConnected(userId);
} catch (e) {
  if (e instanceof UnauthorizedException) {
    // e.message is the Graph error; token/scope errors -> reconnect
  } else throw e;
}

Prevention

When it happens

Trigger: Microsoft Graph returned 401 (token expired/revoked), 403 (insufficient scope), 404 (calendar deleted), or a throttling 429 that the adapter recorded as a permanent error; the refresh token can no longer be renewed; the user's mailbox/calendar was deleted.

Common situations: Refresh token expired (90 days inactive), consent revoked, calendar deleted, scope reduced after the fact, transient Graph outage recorded into the calendar's error field.

Related errors


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