The Graph site permissions endpoint lists application grants: Sites.FullControl.All buys an empty array
GET /sites/{siteId}/permissions lists the grants held by applications on a site, not the people who can open it, and returns an empty array on any tenant that never used Sites.Selected. Microsoft documents Sites.FullControl.All as its least privileged permission. Two cheaper reads answer the real question.
Microsoft Graph publishes an endpoint whose path reads like the answer to the first question a Copilot readiness review asks, which is who can open a given SharePoint site. GET /sites/{siteId}/permissions answers something else. It lists the grants held by applications on that site, the objects written so that Sites.Selected access works, and on a site where no such grant was ever created it returns an empty collection. The least privileged permission Microsoft documents for that read, delegated and application alike, is Sites.FullControl.All, with no lower alternative published beside it.
Last verified: 2026-09-05.
What the documentation says
The Graph reference for listing permissions on a site prints two usable rows in its permissions table. Delegated work or school account: least privileged Sites.FullControl.All, higher privileged "Not available." Application: least privileged Sites.FullControl.All, higher privileged "Not available." The same page opens with an Important block that narrows the read further: "Using the LIST method to retrieve SharePoint subsites' permissions isn't supported at this time."
The documented response settles what the collection holds. Both objects in the example carry a grantedToIdentitiesV2 array whose single member is an application, one named Contoso Time Manager App with a role of read, the other named Fabrikam Dashboard App with a role of write. No user, no group and no SharePoint permission level appears anywhere in it. The permission resource type defines itself in one sentence: "The Permission resource provides information about a sharing permission granted for a DriveItem resource."
Why the collection is usually empty is on the Selected permissions overview. A Selected grant exists only because an administrator wrote it: "Selected scopes require an explicit assignment action; an application consented for Lists.SelectedOperations.Selected would initially have no access." The same page states why the read is priced where it is: "Because you can grant full control permissions to a site collection by using Sites.Selected, this requirement is necessarily high." A POST to that same path is what the price is for, and its documented body hands a named application a role on the site collection, up to fullcontrol.
Two accepted answers on Microsoft Q and A state the consequence plainly. Neither is documentation, so both are quoted as corroboration rather than as authority. On a question about an empty response, answer 1844130: "Use this method to obtain application permissions; not site user permissions." and "The default return value is an empty array, unless you have previously created permissions." On a question about SharePoint REST and full control, answer 12909959 draws the line the permissions table is expressing: "Content enumeration can use Read. Permission enumeration is treated as privileged."
Why the misreading is easy
Three things line up behind it. The path carries the word permissions and hangs off a site, so it reads as the site's permissions. The permissions table shows the strongest SharePoint grant Microsoft publishes with nothing beside it, which reads as a price to be paid rather than as a signpost pointing at a different endpoint. And the permissions reference gives Sites.FullControl.All the display text "Have full control of all site collections" and the application description "Allows the app to have full control of all site collections without a signed in user", which sounds like a grant that can see who has access to what. It can. This endpoint still does not report it.
Three reads, three prices, three different objects
| Read | Least privileged application permission | What the response carries |
|---|---|---|
| GET /sites/{siteId}/permissions | Sites.FullControl.All, no lower alternative published | Grants held by applications, created for Sites.Selected. Empty by default |
| GET /sites/{siteId}/lists/{listId}/permissions | Sites.Read.All | The same object class at list scope. The documented example is again a single application grant |
| GET /drives/{driveId}/items/{itemId}/permissions | Files.Read.All | Effective sharing on one item: sharing links, inherited grants, and grantedToV2 identities carrying siteUser |
The list scoped read moves the price from full control of every site collection to Sites.Read.All, whose published description is "Allows the app to read documents and list items in all site collections without a signed in user". It does not move the object class: the response example on the list permissions page is one application grant, the same shape as the site one. The read that returns people is the driveItem one, which the driveItem permissions page opens by calling "List the effective sharing permissions on a driveItem" and prices at Files.Read.All for an application. The same page bounds what any caller gets back: "For a non-owner caller, only the sharing permissions that apply to the caller are returned."
What the misreading costs
Sites.FullControl.All requires admin consent and grants full control of every site collection in the tenant, which is read, write, delete and permission change on every site. The contrast sits in the same reference: Sites.Selected is described as access to "a subset of site collections", and Sites.FullControl.All names no site at all. At site level that consent buys a collection which, on a tenant that never used Sites.Selected, is empty. The engagement then still has no answer to the question it was scoped around, and it has to ask the customer's security reviewer to approve the broadest SharePoint grant Microsoft publishes in order to get that nothing. Where the reviewer refuses, which is the correct call, the work stops at the consent screen. Where the reviewer agrees, the tenant carries a standing full control grant for the life of the app registration.
The read that answers the actual question
Per person visibility comes from the customer's own tenant, under the customer's own entitlement. The SharePoint admin centre carries Data access governance reports under Reports, and the one that answers who can open what is Site permissions for users, which "lists all sites a user can access and helps admins determine whether they can access the entire site or specific sections, granted directly to the user or indirectly through groups". Its download carries one row per site with Is site shared, Items with direct access count and Items with indirect access count, alongside the user principal name and the Entra object id. That is the shape a readiness review needs, and producing it consents no Graph permission at all.
The entitlement is on the prerequisites page and it is written as two halves. First a base subscription from a named list: Office 365 E3, E5 or A5, Microsoft 365 E1, E3, E5 or A5, or the GCC, GCC High and DoD variants. Then one of three further conditions, the first of which is that "At least one user in your organization is assigned a Microsoft Copilot license", with the same page adding that the user "doesn't need to be a SharePoint administrator". Reading the reports needs one of the two roles that page names, SharePoint Administrator or SharePoint Advanced Management Administrator.
flowchart TD
accTitle: Choosing the read that answers a SharePoint access question
accDescr: A single question splits by subject. An application grant is read at the site permissions endpoint. A person is read either at the driveItem permissions endpoint for one item, or from the admin centre snapshot report for the whole tenant.
A{Whose access} -->|an application| B[Site permissions read]
A -->|a person| C{Scope of answer}
C -->|one item| D[driveItem permissions]
C -->|whole tenant| E[Admin centre report]How to check it
Three read-only requests, run in Graph Explorer as a signed in administrator against a site that has never had a Sites.Selected grant written to it. The first is the claim under test, the second and third are the alternatives. The first needs the delegated Sites.FullControl.All consent, which the permissions reference marks as requiring admin consent, and without it the response is an error rather than the empty collection the check is looking for.
GET https://graph.microsoft.com/v1.0/sites/{siteId}/permissions
GET https://graph.microsoft.com/v1.0/sites/{siteId}/lists/{listId}/permissions
GET https://graph.microsoft.com/v1.0/drives/{driveId}/items/{itemId}/permissions
The first returns HTTP 200 with a value array that is empty, and an empty array is the finding rather than an error. The third returns objects with a link facet for sharing links and a grantedToV2 object carrying a user and a siteUser, which is the identity information the site level read never had, and it returns that in full only for a caller who owns the item. The same page also records that "The permissions relationship of DriveItem cannot be expanded as part of a call to get DriveItem or a collection of DriveItems", so item level coverage is one request per item.
Recommendation
Do not ask a customer for Sites.FullControl.All in order to read who can reach a site. Where an inventory of Sites.Selected grants is genuinely wanted, the site level endpoint is the only one that returns them, so run it delegated as the tenant's own administrator and record it as a one time read rather than standing consent on an app registration. For exposure on specific content, use the driveItem read at Files.Read.All. For who can open what across the estate, have the customer generate the Data access governance reports and read the export.
Trade-offs
The export path costs time. The site permissions for users report captures data from up to 48 hours before generation, is capped at five reports, can be run again every 30 days, and requires the organisation wide site permissions report to have been generated at least once, all four stated on its own page. The driveItem path costs calls, because permissions cannot be expanded across a collection, so a library of 200000 items is 200000 requests and is not a read that finishes inside an engagement window. The site level read is a single cheap call that returns almost nothing, and that combination is why it survives in plans that were never checked against a response.
Limitations
An empty array proves that no Sites.Selected grant exists on that site at that moment. It does not prove that no application can reach the site. An application holding Sites.Read.All, Sites.ReadWrite.All or Sites.FullControl.All reaches every site with no object appearing in this collection at all, because those are tenant wide consents rather than per site assignments, so this read is not an application inventory either. The LIST method also does not cover subsites, which the page states in its own Important block, so a subsite's grants sit outside the read entirely. Nor is a non-empty collection proof that an administrator assigned anything deliberately: the permissions reference records that an application holding Sites.Create.All "will be granted Sites.Selected(application) + FullControl to the newly created site" on every site it creates. The driveItem alternative carries its own boundary, quoted above, because a caller who does not own the item sees only the sharing permissions that apply to that caller, so a delegated read by an administrator who is not the owner is a partial picture of who can reach the file.
The reports carry their own boundaries. The Data access governance page records that they "are currently unavailable for Microsoft 365 operated by 21Vianet, even if you have the required licenses", and that an organisation on Microsoft 365 E5 without SharePoint Advanced Management reaches the reporting surface with less in it: "The reports don't provide snapshot reports or remedial actions. Activity reports are available but can return only up to 10,000 sites." Activity reports cover 28 days. A snapshot is a point in time, so a permission granted after it was generated is not in it.
Where it does not apply
None of this reaches SharePoint Server on premises, which these Graph pages do not cover, or SharePoint Embedded containers, where the driveItem page notes that the FileStorageContainer.Selected permission and container type permissions are required on top. The Business plan case is unsettled on the prerequisites page itself. Its base subscription list names no Microsoft 365 Business plan, while a later sentence on the same page carries no base subscription qualifier: "If your organization has at least one Microsoft Copilot license assigned to a user, your SharePoint administrators automatically receive access to SharePoint Advanced Management capabilities to support your Copilot deployment". A Business Premium tenant holding a Copilot licence sits between those two sentences, so confirm the admin centre in that tenant before planning either way. And on a tenant that genuinely runs Sites.Selected at scale, the site level read stops being empty and becomes the correct inventory of which application holds which role on which site.
Which read gets consented on the way into a Copilot deployment is the argument worth having, and the Copilot Readiness Audit is the ITSailor engagement built around the customer generating the privileged report and this practice reading the export.
Sources and further reading
- List permissions (site)
- permission resource type
- Overview of Selected permissions in OneDrive and SharePoint
- List permissions on a list
- List sharing permissions on a driveItem
- Microsoft Graph permissions reference
- Data access governance reports for SharePoint and OneDrive sites
- Discover sites accessible by a given user with the snapshot report
- Prerequisites for SharePoint Advanced Management
- Microsoft Q and A 1844130, how can I get permissions from SharePoint site using MS Graph
- Microsoft Q and A 12909959, SharePoint REST APIs and FullControl permissions
Turn the trade-off into a scoped brief.
Share the constraints that differ in your environment. Michal will identify the next check needed before a delivery decision.
Start a scoped briefMore from Ops Log
A CSP partner may not sell to itself or an affiliate: Microsoft names two own-use routes instead
Microsoft's CSP documentation says partners are barred by contract from selling Microsoft or third-party offers to themselves or an affiliate as end customer. The same page names two own-use routes: a Shared Services tenant for Azure, or a separate tenant bought through another CSP partner.
The Google Workspace scope for reading a leaver's app grants is not read-only
Listing a Google Workspace user's third-party app tokens needs admin.directory.user.security, the same scope that deletes them, and Google lists no read-only variant. Microsoft Graph lists the equivalent grants with Directory.Read.All. Treat the Google credential as a revocation key.
The Azure role called Cost Management Reader is not read-only
Cost Management Reader is described as able to view cost data, yet its action list carries a Microsoft.Support wildcard that includes creating and updating support tickets. The built-in Reader role matches it on every Cost Management feature and cannot write a ticket.