How can a delegated app distinguish a room mailbox calendar from a user's calendar in GET /me/calendars?

Shota Daido 0 Reputation points
2026-09-03T08:42:25.94+00:00

We are building a scheduling application. After a user connects their Outlook calendar with delegated permissions (offline_access, User.Read, Calendars.ReadWrite), we list the calendars the user has added and offer the people among them as suggestions. We need to exclude resource (room / equipment) mailboxes, but we cannot find a supported way to do that.

1. The calendar resource carries no type information

Comparing a colleague's shared calendar with a room mailbox calendar in the same tenant, every property that could distinguish them has the same value: canEdit = false, isSharedWithMe = true, isShared = false, canShare = false, canViewPrivateItems = false, isDefaultCalendar = false, isTallyingResponses = true, isRemovable = true. owner.address is an ordinary SMTP address in both cases.

We checked the full property set in both v1.0 and beta. owner is documented as "the person who shared that calendar with the user", so it carries no type information, and allowedOnlineMeetingProviders reflects Teams availability rather than mailbox type (a real user's own calendar can have exactly the same value as a room).

2. findRooms returned 403 with the documented least-privileged permission

The reference for "user: findRooms" (beta) lists User.Read as the least privileged delegated (work or school) permission, with User.Read.All and User.ReadBasic.All as higher privileged.

In a Microsoft 365 Business Basic tenant, GET https://graph.microsoft.com/beta/me/findRooms with User.Read already consented returned 403 Forbidden with { "error": { "code": "ErrorAccessDenied", "message": "Access is denied. Check credentials and try again." } }.

After additionally consenting User.Read.All, the identical request returned 200 OK with the expected collection of emailAddress objects.

In a second, unrelated tenant the same call succeeded for a non-admin user, but that tenant already had User.ReadBasic.All and Directory.Read.All admin-consented tenant-wide for the calling app. That is consistent with a directory-read permission being what actually authorizes the call.

Questions

a) Is User.Read alone actually sufficient for findRooms, or is a directory-read permission required in practice? The error code returned is ErrorAccessDenied, which is an Exchange-side code rather than the usual authorization error, so from the caller side we cannot tell whether this is a missing scope or a tenant / mailbox configuration issue.

b) If a directory-read permission is required, is User.ReadBasic.All sufficient? It is the only one of the three that does not require admin consent by default, which matters because our users connect their own calendar without involving a tenant administrator.

c) Is there any supported way to classify a calendar returned by GET /me/calendars as belonging to a room or equipment mailbox, without admin consent and without reading event contents?

We are aware of these options and their limitations:

  • GET /places/microsoft.graph.room requires Place.Read.All, which requires admin consent.
  • GET /me/people returns personType with class "Other" and subclass "Room", but "Room" is not listed as an enumerated value in the personType documentation, and the API returns only "relevant" people, so there is no guarantee that every room in the user's calendar list is covered.
  • attendee.type "resource" and location.locationType "conferenceRoom" require reading event contents, which we deliberately do not do.

We would rather follow a supported approach than depend on any of the above, so any guidance on the intended way to do this would be appreciated.

Microsoft Security | Microsoft Graph
0 comments No comments

Your answer

Answers can be marked as 'Accepted' by the question author and 'Recommended' by moderators, which helps users know the answer solved the author's problem.