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
- Read the forwarded message; it is the exact Graph error and points at the fix (expiry -> reconnect, scope -> update app registration).
- Re-run the Office 365 connect flow to mint a fresh credential with a valid refresh token.
- 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
- Refresh Microsoft refresh tokens by regular activity to avoid 90-day expiry.
- Keep the app registration scopes aligned with what Cal requests.
- Monitor connectedCalendar.error.message in the UI.
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
- Credentials for office_365_calendar not found.
- Invalid office 365 calendar credentials.
- Office 365 calendar not connected.
- Invalid Access token.
- ${icsCalendar.error?.message}
AI-assisted analysis of calcom/cal.diy@176037d0af (2026-08-12).
Data as JSON: /api/errors/6a4a750d73c90630.
Report an issue: GitHub.