There is no Microsoft Graph API that pulls list of site users. With PnP.PowerShell it’d be easy: Get-PnPUser. Here is the Graph site resource type – there is nothing like that. For group-based sites (e.g. Teams, Viva Engage communities – you get get group members via MS Graph API, but that’s different. So, what do we do to get all SharePoint site users with MS Graph API?
The answer is very simple. Every SharePoint site has a hidden (system) list called User Information List. And that is exactly what you need. So, you’d get list “User Information List” and get all it’s items via graph API:
Use MS Graph Get List API to get “User Information List” list
Use MS Graph Get List Items API to get all list item – which would be site users 🙂
P.S. using Get-PnPUser against sites with large population could fail with HttpClient.Timeout error, so in such cases we’d use the same approach – get items from User Information List via Get-PnPListItem -List “User Information List” -PageSize 1000. See details in the “Use Get-PnPUser correctly for large sites” KBA.
Initially, *.Selected permissions scope in Graph API only restricted app access to a site collection. Now, it also supports lists, items, folders, and files, and all ‘Selected’ scopes work in both delegated and application modes. This means more granular SharePoint content access for custom apps using the Graph API. Below, I’ll deep-dive into SelectedOperations.Selected permissions and include Python and PowerShell samples for using them.
Client Application
There must be an App Registration for client application that would have access to a SharePoint list.
At this moment (we have a client app and secret, and “” API permissions, but did not provide for this app access to specific sites or libraries) – we should be able to authenticate to Microsoft 365, but not able to get any kind of data (we can get token, but other call to Graph API would return 403 error – Error: b'{“error”:{“code”:”accessDenied”,”message”:”Request Doesn\’t have the required Permission scopes to access a site.”,):
This app registration should have Microsoft Graph “Lists.SelectedOperations.Selected” (or “ListItems.SelectedOperations.Selected” or “Files.SelectedOperations.Selected”) API permissions consented. Registered app Microsoft Graph API permissions should look like:
I use Python console app as a client application. Link to the code at the GitHub is shared below under References, but the core part of the Python code is (I do not use any Microsoft or other libraries here, just plain requests to Microsoft Graph for authentication and for data):
import requests
import json
from secrets import clientSc, clientId, tenantId, siteId, listId
# specify client id, client secret and tenant id
# clientId = ""
# clientSc = ""
# tenantId = ""
apiUri = "https://login.microsoftonline.com/" + tenantId + "/oauth2/v2.0/token"
body = {
"client_id" : clientId,
"client_secret" : clientSc,
"scope" : "https://graph.microsoft.com/.default",
"grant_type" : "client_credentials"
}
try:
response = requests.post(apiUri, data=body)
token = json.loads(response.content)["access_token"]
except:
print("Error: ", json.loads(response.content)["error_description"])
exit()
print("Got token: ", token[0:10], "...")
headers={'Authorization': 'Bearer {0}'.format(token)}
# Get specific site list
print("Geting specific site list")
# graph_url = 'https://graph.microsoft.com/v1.0/sites/' + siteId + '/lists/' + listId
graph_url = 'https://graph.microsoft.com/beta/sites/' + siteId + '/lists/' + listId
graphResponse = requests.get( graph_url, headers=headers )
print(" Response status code: ", graphResponse.status_code)
if graphResponse.status_code == 200:
list = json.loads(graphResponse.content)
print(" List display name: ", list["displayName"])
Below is the sample Python code to authenticate against Microsoft 365 as current user with MSA library and to call Microsoft Graph API – specifically get SharePoint Site, get Site lists with requests library.
But first, you have to have an App Registration in Azure (Entra ID) with delegated permissions consented and authentication configured.
Delegated Permissions
If your solution needs access to all SharePoint sites – consider Sites.FullControl.All or Sites.Manage.All or Sites.ReadWrite.All or Sites.Read.All depending on access level you need. Effective permissions would be an intersection (minimum) from both – permissions configured for app registration and permissions current user have. Once consented at the app registration – these permissions will work right away.
If your solution needs access to a one (or a few) SharePoint sites – consider Sites.Selected API permissions as it will scope down access to the only sites that are required for your solution to work. Remember, Sites.Selected API permissions, even consented at the app registration, require second step – SharePoint admin should provide (read or write or manage or fullcontrol) permissions for the app registration to a specific site or sites.
Authentication
You’d also need to configure authentication blade. How? It depends on the kind of application you are building etc. For example for native apps I do: – add platform – “Mobile and Desktop app” – select “https://login.microsoftonline.com/common/oauth2/nativeclient” – select “msal096fd951-7285-4e4f-9c1f-23a393556b19://auth (MSAL only)” – add custom Redirect URI: “http://localhost”
This config works for Python code below
Python Code
You’d need to install/import the libraries: json, configparser, msal, requests
Here is the code:
import json
import configparser
import msal
import requests
config = configparser.ConfigParser()
config.read('config.cfg')
client_id = config.get('delegated','clientId')
authority = config.get('delegated','authority')
scopes = config.get('delegated','scope').split()
siteId = config.get('delegated','siteId')
print( client_id)
print( authority)
print( scopes)
print( siteId)
global_token_cache = msal.TokenCache()
global_app = msal.PublicClientApplication(
client_id,
authority=authority, # For Entra ID or External ID
token_cache=global_token_cache,
)
def acquire_and_use_token():
# The pattern to acquire a token looks like this.
result = None
result = global_app.acquire_token_interactive(scopes)
if "access_token" in result:
print("Token was obtained from:", result["token_source"]) # Since MSAL 1.25
# print("Token acquisition result", json.dumps(result, indent=2))
return result["access_token"]
else:
print("Token acquisition failed", result) # Examine result["error_description"] etc. to diagnose error
return None
token = acquire_and_use_token()
http_headers = {'Authorization': 'Bearer ' + token,
'Accept': 'application/json',
'Content-Type': 'application/json'}
graph_url = 'https://graph.microsoft.com/v1.0/sites/' + siteId + '?$select=id,webUrl,displayName'
siteResponse = requests.get(graph_url, headers=http_headers)
print(siteResponse.status_code)
site = json.loads(siteResponse.content)
# print("Site (raw) : ")
# print(site)
print("Site webUrl : ", site["webUrl"])
print("Site displayName : ", site["displayName"])
# Lists
graph_url = 'https://graph.microsoft.com/v1.0/sites/' + siteId + '/lists'
listsResponse = requests.get(graph_url, headers=http_headers)
print(listsResponse.status_code)
lists = json.loads(listsResponse.content)
# print("Site lists (raw):")
# print(lists)
print("Site lists:")
for list in lists["value"]:
print(" Display Name:", list["displayName"])
print(" Id:", list["id"])
print(" Web Url:", list["webUrl"])
print(" Created Date:", list["createdDateTime"])
print(" Last Modified Date:", list["lastModifiedDateTime"])
Application permissions
If your scenario is to call Graph API from Python with application permissions (aka unattended or daemon app) – the main difference is authentication. It is described here. It also requires App registration configured like this.
Microsoft says “Initially, Sites.Selected existed to restrict an application’s access to a single site collection. Now, lists, list items, folders, and files are also supported, and all Selected scopes now support delegated and application modes.”. In other words, Microsoft started supporting even more granular access to SharePoint content from custom applications using Microsoft Graph API.
In the article below I will deep-dive into SelectedOperations.Selected permissions, including PowerShell scripts to provide granular permissions (if you want to know more about Sites.Selected Graph API permissions – it’s here).
Why do we need SelectedOperations.Selected Graph API permissions
Granular permissions available are:
Lists.SelectedOperations.Selected
Provides application access to a specific list
ListItems.SelectedOperations.Selected
Provides application access to one or more list items, files, or folders
Files.SelectedOperations.Selected
Provides application access to to one or more files or library folders
Set of SelectedOperations permissions is exactly what Microsoft promised a few years ago. And this is great, as we really need granular access to the content in SharePoint sites. I’ve been supporting enterprise SharePoint for more than 15 years now, and I know that it was always a concern if application in fact requires access to a specific list/library or a folder or even one file, but admins provide access to entire site collection.
Especially, I believe, this feature will become more in demand due to Copilot for Microsoft 365. Currently – it’s mostly developers and data analytics who needs unattended application access to SharePoint, but sooner or later regular users powered with m365 Copilot license will start creating autonomous agents…
Here is the screenshot of a Copilot agent authentication/access to SharePoint data using client id and secret:
So below is my research and lab setup, guide with screenshots and PowerShell scripts on how to provide granular (to library/list or folder, or even just one document or list item) Graph API permissions to SharePoint. This KBA is for Microsoft 365 SharePoint administrators. I’m planning to have a separate KBA for developers on how to use granular permissions.
Prerequisites
Admin App
First, we need an Admin App – an app we will be using to provide permissions.
The only requirement to the app is: the app should have Microsoft.Graph Sites.FullControl.All Graph API permissions consented:
Target Site, List. Item
Your client will probably provide you with the link to the SharePoint resource they need access to. But to do both – to provide granular permissions – or to access one specific list, folder or item – we need to know this site id, list id, item id. So it’ll be our admins job to decompose the link and get Ids to provide access, then share these Ids with the client with some instructions on how to call Graph API to get access.
For this lab/demo setup, I have created three sites under Microsoft Teams (group-based Teams-connected SharePoint sites), then test list and test library in each, like this:
Client Application
There must be an App Registration for client application – application that will have access to Test-List-01 and Test-Lib-01 only. This app registration should have Microsoft Graph “Lists.SelectedOperations.Selected” or “ListItems.SelectedOperations.Selected” or “Files.SelectedOperations.Selected” API permissions consented. Example below has Lists:
Providing selectedoperations permissions
PowerShell script to provide selectedoperations.selected access for an app to a specific list would be as below. Here we use plain calls to MS Graph API. Full script for your refence is available at GitHub, but here is the essential part:
(Update from 2025-10-28) SelectedOperations permissions are still in beta. E.g. I have to call “https://graph.microsoft.com/beta/sites/$targetSiteId/lists/$targetSiteListId/permissions” When trying to call “/v1.0/sites/$targetSiteId/lists/$targetSiteListId/permissions” it says "code": "BadRequest", "message": "Resource not found for the segment \u0027permissions\u0027."
The “/beta/sites/$targetSiteId/lists/$targetSiteListId/permissions” API returns not only applications permissions, but also user’s permissions!
TBD: I’m not sure if it’s a bug or my incorrect setup, but I noticed that if I provide access for the app to the list – app can read site.
There are many scenarios for SharePoint or Teams automations, and in most cases you need to run some code on scheduled basis (e.g. every 5 minutes or every 24 hours etc.). This is where timer-triggered Azure Functions might help. In this article I will provide use cases, overview of the whole scenario and technical setup, and provide links to the detailed step-by-step guides for configuring different parts of the entire solution.
Possible scenarios
Possible scenarios (end-user-oriented):
Create site upon user request
Convert site to a HUB site upon user request
Set site search scope upon user request
Setup site metadata (site custom properties)
Request usage reports/analytics
Possible scenarios (admin-oriented):
Provide temporary access to the site (e.g. during troubleshooting)
Provide Sites.Selected permissions for the App to the Site
Disable custom scripts or ensure custom scripts are disabled
Enable custom scripts (e.g. during site migration)
Monitor licenses – available, running out etc.
Typical setup
Front-end
SharePoint site works as a front-end. You do not need to develop a separate web application, as It’s already there, with reach functionality, secured and free.
The site can have: – one or more lists to accept intake requests – Power Apps to customize forms – Power Automate to implement (e.g. approval) workflows, send notifications etc. – site pages as a solution documentation – libraries to store documents provided as response to requests
You can provide org-wide access to the site if your intention is to allow all users to submit requests or secure the site if you want to accept requests only from a specific limited group of people.
Back-end
Timer-triggered Azure Function works as a back-end. The function can be scheduled to run based on job specific requirements (e.g. every 5 or 10 minutes, or daily or weekly etc.). The function can be written in PowerShell, C#, Python etc.
The function’s logic is to
read SharePoint list, iterate through items to get intake requests
validate request eligibility
perform action
share results (e.g. update intake form, send e-mail, save document to library etc.)
Configuration
There should not be an issue to setup a front-end. You’d just need a solid SharePoint and Power Platform skills.
For the back-end the solution stack would include the following tools/skills: – Azure subscription to host solution – Registered Apps to configure credentials and API access permissions – Azure Function App to actually run the code – Azure Key Vault to securely save credentials – programming skills in language/platform of choice – SharePoint API, Microsoft Graph API
Python code samples published by Microsoft at the Microsoft Graph API reference pages use GraphServiceClient module. But you also can use just requests module and call Microsoft graph API directly, using requests.post or requests.get methods. Here I’m sharing my Python code samples.
In 2021 Microsoft implemented “Sites.Selected” Graph API permissions to allow application access (without a signed in user) to specific sites (entire site only). In 2024 Microsoft implemented granular access – to specific list/libraries, as well as to specific documents/files and list items. Now name convention is *.SelectedOperations.Selected. Permissions come in two flavors – delegated and application:
Files.SelectedOperations.Selected – Allow the application to access a subset of files (files explicitly permissioned to the application). The specific files and the permissions granted will be configured in SharePoint Online or OneDrive.
ListItems.SelectedOperations.Selected – Allow the application to access a subset of lists. The specific lists and the permissions granted will be configured in SharePoint Online.
Lists.SelectedOperations.Selected – Allow the application to access a subset of lists. The specific lists and the permissions granted will be configured in SharePoint Online.
Microsoft Graph connectors allow your organization to index third-party data into Microsoft Graph. Microsoft Graph connectors enable Microsoft 365 Copilot better as it has more information relevant to your organization to answer prompts.
According to Microsoft, Microsoft 365 will soon include a new service plan, Graph Connectors Search with Index, offering a 50 million item index limit per tenant at no cost. Rollout starts September 2024.
Previously, to index third-party data into Microsoft Graph through Microsoft Graph connectors, you either needed to have a built-in entitlement through specific licenses (e.g., 500 items of index quota per Microsoft Copilot for Microsoft 365 license) or purchase add-on quota. With this change, the index quota per license entitlement is removed, as is add-on cost for additional quota. You now receive an entitlement of 50 million items for each tenant.
Each entity (or record) from the source data system that you add to Microsoft Graph can be considered an item which then shows up as a unique citation in Copilot’s responses, as a unique search result in Microsoft Search, etc. Depending on the type of data source, 1 item is –
1 document (word, excel, ppt, pdf, etc.) in file share
1 wiki page in Confluence
1 webpage in a website
1 ticket/issue in Jira
Total quota utilized is calculated in terms of total items stored in the index. Updates/changes to an item are not counted in any manner. There are no cost implications of updating an item multiple times. It still counts as 1 item only.
Applicable to subscriptions: Office 365 E1, Office 365 E3, Office 365 E5, Microsoft 365 E3, Microsoft 365 E5, Microsoft 365 F1, Microsoft 365 F3, Office 365 F3, Microsoft 365 Business Basic, Microsoft 365 Business Standard, Microsoft 365 Business Premium, Office 365 G1, Office 365 G3, Office 365 G5, Microsoft 365 G3, Microsoft 365 G5, Office 365 A3, Office 365 A5, Microsoft 365 A3, Microsoft 365 A5
secrets is a Python file where I assign client secret to variable clientSc (so my secret is not shared on github). This is ok for demo purposes but generally, you should not hard-code secrets but keep secrets somewhere safe (Vault).
There is a Microsoft.Graph PowerShell module provided by Microsoft which simplifies usage of Microsoft Graph API. Below is how to authenticate to MS Graph and how to search within SharePoint and Teams Microsoft 365 content using Microsoft.Graph PowerShell module.
For daemon app authentication we need a certificate configured in Azure App and installed on the user machine. Daemon app authentication code sample (please specify your tenant id, app (client) id and certificate thumbprint: