{"record":{"id":"6a4a750d73c90630","repo":"calcom/cal.diy","slug":"office365calendar-error-message","errorCode":null,"errorMessage":"${office365Calendar.error?.message}","messagePattern":"\\$\\{office365Calendar\\.error\\?\\.message\\}","errorType":"http","errorClass":"UnauthorizedException","httpStatus":401,"severity":"error","filePath":"apps/api/v2/src/platform/calendars/services/outlook.service.ts","lineNumber":110,"sourceCode":"    );\n\n    if (!office365CalendarCredentials) {\n      throw new BadRequestException(\"Credentials for office_365_calendar not found.\");\n    }\n\n    if (office365CalendarCredentials.invalid) {\n      throw new BadRequestException(\"Invalid office 365 calendar credentials.\");\n    }\n\n    const { connectedCalendars } = await this.calendarsService.getCalendars(userId);\n    const office365Calendar = connectedCalendars.find(\n      (cal: { integration: { type: string } }) => cal.integration.type === OFFICE_365_CALENDAR_TYPE\n    );\n    if (!office365Calendar) {\n      throw new UnauthorizedException(\"Office 365 calendar not connected.\");\n    }\n    if (office365Calendar.error?.message) {\n      throw new UnauthorizedException(office365Calendar.error?.message);\n    }\n\n    return {\n      status: SUCCESS_STATUS,\n    };\n  }\n\n  async getOAuthCredentials(code: string) {\n    const scopes = [\"offline_access\", \"Calendars.Read\", \"Calendars.ReadWrite\"];\n    const { client_id, client_secret } = await this.calendarsService.getAppKeys(OFFICE_365_CALENDAR_ID);\n\n    const toUrlEncoded = (payload: Record<string, string>) =>\n      Object.keys(payload)\n        .map((key) => `${key}=${encodeURIComponent(payload[key])}`)\n        .join(\"&\");\n\n    const body = toUrlEncoded({\n      client_id,","sourceCodeStart":92,"sourceCodeEnd":128,"githubUrl":"https://github.com/calcom/cal.diy/blob/176037d0afbe572f870a3c702985e7cd83fe6c0c/apps/api/v2/src/platform/calendars/services/outlook.service.ts#L92-L128","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"const { connectedCalendars } = await calendarsService.getCalendars(userId);\nconst o365 = connectedCalendars.find(c => c.integration.type === OFFICE_365_CALENDAR_TYPE);\nif (o365?.error?.message) {\n  // surface o365.error.message and offer reconnect\n}","typeGuard":null,"tryCatchPattern":"try {\n  await outlookService.checkIfCalendarConnected(userId);\n} catch (e) {\n  if (e instanceof UnauthorizedException) {\n    // e.message is the Graph error; token/scope errors -> reconnect\n  } else throw e;\n}","preventionTips":["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."],"tags":["outlook","office365","upstream","api-v2"],"backgroundTag":null,"analyzedSha":"176037d0afbe572f870a3c702985e7cd83fe6c0c","analyzedAt":"2026-08-12T19:12:41.464Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}